diff --git a/frontend/apps/filemanager/filemanagerbookinfo.lua b/frontend/apps/filemanager/filemanagerbookinfo.lua index f7d8fc6c2..3774db061 100644 --- a/frontend/apps/filemanager/filemanagerbookinfo.lua +++ b/frontend/apps/filemanager/filemanagerbookinfo.lua @@ -15,6 +15,7 @@ local InputDialog = require("ui/widget/inputdialog") local TextViewer = require("ui/widget/textviewer") local UIManager = require("ui/uimanager") local WidgetContainer = require("ui/widget/container/widgetcontainer") +local Utf8Proc = require("ffi/utf8proc") local filemanagerutil = require("apps/filemanager/filemanagerutil") local lfs = require("libs/libkoreader-lfs") local util = require("util") @@ -266,6 +267,25 @@ function BookInfo.getDocProps(file, book_props, no_open_document) return BookInfo.extendProps(book_props, file) end +function BookInfo:findInProps(book_props, search_string, case_sensitive) + for _, key in ipairs(self.props) do + local prop = book_props[key] + if prop then + if key == "series_index" then + prop = tostring(prop) + elseif key == "description" then + prop = util.htmlToPlainTextIfHtml(prop) + end + if not case_sensitive then + prop = Utf8Proc.lowercase(util.fixUtf8(prop, "?")) + end + if prop:find(search_string) then + return true + end + end + end +end + -- Shows book information for currently opened document. function BookInfo:onShowBookInfo() if self.document then diff --git a/frontend/apps/filemanager/filemanagerfilesearcher.lua b/frontend/apps/filemanager/filemanagerfilesearcher.lua index 131af0774..4f7c74947 100644 --- a/frontend/apps/filemanager/filemanagerfilesearcher.lua +++ b/frontend/apps/filemanager/filemanagerfilesearcher.lua @@ -5,7 +5,6 @@ local ConfirmBox = require("ui/widget/confirmbox") local DocSettings = require("docsettings") local DocumentRegistry = require("document/documentregistry") local FileChooser = require("ui/widget/filechooser") -local FileManagerBookInfo = require("apps/filemanager/filemanagerbookinfo") local InfoMessage = require("ui/widget/infomessage") local InputDialog = require("ui/widget/inputdialog") local Menu = require("ui/widget/menu") @@ -34,7 +33,7 @@ function FileSearcher:onShowFileSearch(search_string) local check_button_case, check_button_subfolders, check_button_metadata search_dialog = InputDialog:new{ title = _("Enter text to search for in filename"), - input = search_string or self.search_value, + input = search_string or self.search_string, buttons = { { { @@ -48,8 +47,8 @@ function FileSearcher:onShowFileSearch(search_string) text = _("Home folder"), enabled = G_reader_settings:has("home_dir"), callback = function() - self.search_value = search_dialog:getInputText() - if self.search_value == "" then return end + self.search_string = search_dialog:getInputText() + if self.search_string == "" then return end UIManager:close(search_dialog) self.path = G_reader_settings:readSetting("home_dir") self:doSearch() @@ -59,8 +58,8 @@ function FileSearcher:onShowFileSearch(search_string) text = self.ui.file_chooser and _("Current folder") or _("Book folder"), is_enter_default = true, callback = function() - self.search_value = search_dialog:getInputText() - if self.search_value == "" then return end + self.search_string = search_dialog:getInputText() + if self.search_string == "" then return end UIManager:close(search_dialog) self.path = self.ui.file_chooser and self.ui.file_chooser.path or self.ui:getLastDirFile() self:doSearch() @@ -126,17 +125,17 @@ function FileSearcher:getList() ["/sys"] = true, } local collate = G_reader_settings:readSetting("collate") - local keywords = self.search_value - if keywords ~= "*" then -- one * to show all files + local search_string = self.search_string + if search_string ~= "*" then -- one * to show all files if not self.case_sensitive then - keywords = Utf8Proc.lowercase(util.fixUtf8(keywords, "?")) + search_string = Utf8Proc.lowercase(util.fixUtf8(search_string, "?")) end -- replace '.' with '%.' - keywords = keywords:gsub("%.","%%%.") + search_string = search_string:gsub("%.","%%%.") -- replace '*' with '.*' - keywords = keywords:gsub("%*","%.%*") + search_string = search_string:gsub("%*","%.%*") -- replace '?' with '.' - keywords = keywords:gsub("%?","%.") + search_string = search_string:gsub("%?","%.") end local dirs, files = {}, {} @@ -161,14 +160,14 @@ function FileSearcher:getList() if self.include_subfolders and not sys_folders[fullpath] then table.insert(new_dirs, fullpath) end - if self:isFileMatch(f, fullpath, keywords) then + if self:isFileMatch(f, fullpath, search_string) then table.insert(dirs, FileChooser.getListItem(f, fullpath, attributes)) end -- Always ignore macOS resource forks, too. elseif attributes.mode == "file" and not util.stringStartsWith(f, "._") and (FileChooser.show_unsupported or DocumentRegistry:hasProvider(fullpath)) and FileChooser:show_file(f) then - if self:isFileMatch(f, fullpath, keywords, true) then + if self:isFileMatch(f, fullpath, search_string, true) then table.insert(files, FileChooser.getListItem(f, fullpath, attributes, collate)) end end @@ -180,36 +179,22 @@ function FileSearcher:getList() return dirs, files end -function FileSearcher:isFileMatch(filename, fullpath, keywords, is_file) - if keywords == "*" then +function FileSearcher:isFileMatch(filename, fullpath, search_string, is_file) + if search_string == "*" then return true end if not self.case_sensitive then filename = Utf8Proc.lowercase(util.fixUtf8(filename, "?")) end - if string.find(filename, keywords) then + if string.find(filename, search_string) then return true end if self.include_metadata and is_file and DocumentRegistry:hasProvider(fullpath) then local book_props = self.ui.coverbrowser:getBookInfo(fullpath) or - FileManagerBookInfo.getDocProps(fullpath, nil, true) -- do not open the document + self.ui.bookinfo.getDocProps(fullpath, nil, true) -- do not open the document if next(book_props) ~= nil then - for _, key in ipairs(FileManagerBookInfo.props) do - local prop = book_props[key] - if prop and prop ~= "" then - if key == "series_index" then - prop = tostring(prop) - end - if not self.case_sensitive then - prop = Utf8Proc.lowercase(util.fixUtf8(prop, "?")) - end - if key == "description" then - prop = util.htmlToPlainTextIfHtml(prop) - end - if string.find(prop, keywords) then - return true - end - end + if self.ui.bookinfo:findInProps(book_props, search_string, self.case_sensitive) then + return true end else self.no_metadata_count = self.no_metadata_count + 1 @@ -218,7 +203,7 @@ function FileSearcher:isFileMatch(filename, fullpath, keywords, is_file) end function FileSearcher:showSearchResultsMessage(no_results) - local text = no_results and T(_("No results for '%1'."), self.search_value) + local text = no_results and T(_("No results for '%1'."), self.search_string) if self.no_metadata_count == 0 then UIManager:show(InfoMessage:new{ text = text }) else diff --git a/frontend/apps/filemanager/filemanagerhistory.lua b/frontend/apps/filemanager/filemanagerhistory.lua index eee9c0e04..9119689e3 100644 --- a/frontend/apps/filemanager/filemanagerhistory.lua +++ b/frontend/apps/filemanager/filemanagerhistory.lua @@ -1,11 +1,15 @@ local BD = require("ui/bidi") local ButtonDialog = require("ui/widget/buttondialog") +local CheckButton = require("ui/widget/checkbutton") local ConfirmBox = require("ui/widget/confirmbox") +local InputDialog = require("ui/widget/inputdialog") local Menu = require("ui/widget/menu") local UIManager = require("ui/uimanager") local WidgetContainer = require("ui/widget/container/widgetcontainer") local Screen = require("device").screen +local Utf8Proc = require("ffi/utf8proc") local filemanagerutil = require("apps/filemanager/filemanagerutil") +local util = require("util") local _ = require("gettext") local C_ = _.pgettext local T = require("ffi/util").template @@ -15,12 +19,12 @@ local FileManagerHistory = WidgetContainer:extend{ } local filter_text = { - all = C_("Book status filter", "All"), - reading = C_("Book status filter", "Reading"), + all = C_("Book status filter", "All"), + reading = C_("Book status filter", "Reading"), abandoned = C_("Book status filter", "On hold"), - complete = C_("Book status filter", "Finished"), - deleted = C_("Book status filter", "Deleted"), - new = C_("Book status filter", "New"), + complete = C_("Book status filter", "Finished"), + deleted = C_("Book status filter", "Deleted"), + new = C_("Book status filter", "New"), } function FileManagerHistory:init() @@ -64,7 +68,7 @@ function FileManagerHistory:updateItemTable() reading = 0, abandoned = 0, complete = 0, deleted = 0, new = 0, } local item_table = {} for _, v in ipairs(require("readhistory").hist) do - if self.filter == "all" or v.status == self.filter then + if self:isItemMatch(v) then if self.is_frozen and v.status == "complete" then v.mandatory_dim = true end @@ -75,18 +79,50 @@ function FileManagerHistory:updateItemTable() end end local title = self.hist_menu_title - if self.filter ~= "all" then - title = title .. " (" .. filter_text[self.filter] .. ": " .. self.count[self.filter] .. ")" + local filter_title + if self.search_string then + filter_title = _("search results") + elseif self.filter ~= "all" then + filter_title = filter_text[self.filter]:lower() + end + if filter_title then + title = title .. T(" (%1: %2)", filter_title, #item_table) end self.hist_menu:switchItemTable(title, item_table, select_number) end +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 + function FileManagerHistory:onSetDimensions(dimen) self.dimen = dimen end function FileManagerHistory:onMenuChoice(item) - require("apps/reader/readerui"):showReader(item.file) + 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) @@ -153,7 +189,7 @@ function FileManagerHistory:onMenuHold(item) }) self.histfile_dialog = ButtonDialog:new{ - title = BD.filename(item.text:match("([^/]+)$")), + title = BD.filename(item.text), title_align = "center", buttons = buttons, } @@ -178,7 +214,7 @@ function FileManagerHistory:MenuSetRotationModeHandler(rotation) return true end -function FileManagerHistory:onShowHist() +function FileManagerHistory:onShowHist(search_info) self.hist_menu = Menu:new{ ui = self.ui, covers_fullscreen = true, -- hint for UIManager:_repaint() @@ -192,6 +228,12 @@ function FileManagerHistory:onShowHist() _manager = self, } + 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 @@ -200,14 +242,14 @@ function FileManagerHistory:onShowHist() self:updateItemTable() self.hist_menu.close_callback = function() if self.files_updated then -- refresh Filemanager list of files - local FileManager = require("apps/filemanager/filemanager") - if FileManager.instance then - FileManager.instance:onRefresh() + if self.ui.file_chooser then + self.ui.file_chooser:refreshPath() end self.files_updated = nil end self.statuses_fetched = nil UIManager:close(self.hist_menu) + self.hist_menu = nil G_reader_settings:saveSetting("history_filter", self.filter) end UIManager:show(self.hist_menu) @@ -227,6 +269,9 @@ function FileManagerHistory:showHistDialog() callback = function() UIManager:close(hist_dialog) self.filter = filter + if filter == "all" then -- reset all filters + self.search_string = nil + end self:updateItemTable() end, } @@ -241,6 +286,15 @@ function FileManagerHistory:showHistDialog() genFilterButton("abandoned"), genFilterButton("complete"), }) + table.insert(buttons, { + { + text = _("Search in filename and book metadata"), + callback = function() + UIManager:close(hist_dialog) + self:onSearchHistory() + end, + }, + }) if self.count.deleted > 0 then table.insert(buttons, {}) -- separator table.insert(buttons, { @@ -269,6 +323,57 @@ function FileManagerHistory:showHistDialog() UIManager:show(hist_dialog) end +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 + function FileManagerHistory:onBookMetadataChanged() if self.hist_menu then self.hist_menu:updateItems() diff --git a/frontend/dispatcher.lua b/frontend/dispatcher.lua index b484cb48a..1f39e767b 100644 --- a/frontend/dispatcher.lua +++ b/frontend/dispatcher.lua @@ -52,6 +52,7 @@ local settingsList = { reading_progress = {category="none", event="ShowReaderProgress", title=_("Reading progress"), general=true}, open_previous_document = {category="none", event="OpenLastDoc", title=_("Open previous document"), general=true}, history = {category="none", event="ShowHist", title=_("History"), general=true}, + history_search = {category="none", event="SearchHistory", title=_("History search"), general=true}, favorites = {category="none", event="ShowColl", arg="favorites", title=_("Favorites"), general=true}, filemanager = {category="none", event="Home", title=_("File browser"), general=true, separator=true}, ---- @@ -266,6 +267,7 @@ local dispatcher_menu_order = { "reading_progress", "open_previous_document", "history", + "history_search", "favorites", "filemanager", ----