|
|
|
local ButtonDialogTitle = require("ui/widget/buttondialogtitle")
|
|
|
|
local CheckButton = require("ui/widget/checkbutton")
|
|
|
|
local CenterContainer = require("ui/widget/container/centercontainer")
|
|
|
|
local DocumentRegistry = require("document/documentregistry")
|
|
|
|
local FileChooser = require("ui/widget/filechooser")
|
|
|
|
local InfoMessage = require("ui/widget/infomessage")
|
|
|
|
local InputDialog = require("ui/widget/inputdialog")
|
|
|
|
local Menu = require("ui/widget/menu")
|
[RFC] Pagination UI shenanigans (#7335)
* Menu/KeyValuePage/ReaderGoTo: Unify the dialogs. (Generally, "Enter page number" as title, and "Go to page" as OK button).
* Allow *tapping* on pagination buttons, too. Added spacers around the text to accommodate for that.
* Disable input handlers when <= 1 pages, while still printing the label in black.
* Always display both the label and the chevrons, even on single page content. (Menu being an exception, because it can handle showing no content at all, in which case we hide the chevrons).
* KVP: Tweak the pagination buttons layout in order to have consistent centering, regardless of whether the return arrow is enabled or not. (Also, match Menu's layout, more or less).
* Menu: Minor layout tweaks to follow the KVP tweaks above. Fixes, among possibly other things, buttons in (non-FM) "List" menus overlapping the final entry (e.g., OPDS), and popout menus with a border being misaligned (e.g., Calibre, Find a file).
* CalendarView: Minor layout tweaks to follow the KVP tweaks. Ensures the pagination buttons are laid out in the same way as everywhere else (they used to be a wee bit higher).
4 years ago
|
|
|
local Size = require("ui/size")
|
|
|
|
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.
2 years ago
|
|
|
local WidgetContainer = require("ui/widget/container/widgetcontainer")
|
|
|
|
local BaseUtil = require("ffi/util")
|
|
|
|
local Utf8Proc = require("ffi/utf8proc")
|
|
|
|
local lfs = require("libs/libkoreader-lfs")
|
|
|
|
local util = require("util")
|
|
|
|
local _ = require("gettext")
|
|
|
|
local Screen = require("device").screen
|
|
|
|
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.
2 years ago
|
|
|
local FileSearcher = WidgetContainer:extend{
|
|
|
|
dirs = nil, -- table
|
|
|
|
files = nil, -- table
|
|
|
|
results = nil, -- table
|
|
|
|
|
|
|
|
case_sensitive = false,
|
|
|
|
include_subfolders = true,
|
|
|
|
}
|
|
|
|
|
|
|
|
local sys_folders = { -- do not search in sys_folders
|
|
|
|
["/dev"] = true,
|
|
|
|
["/proc"] = true,
|
|
|
|
["/sys"] = true,
|
|
|
|
}
|
|
|
|
|
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.
2 years ago
|
|
|
function FileSearcher:init()
|
|
|
|
self.dirs = {}
|
|
|
|
self.files = {}
|
|
|
|
self.results = {}
|
|
|
|
end
|
|
|
|
|
|
|
|
function FileSearcher:readDir()
|
|
|
|
local ReaderUI = require("apps/reader/readerui")
|
|
|
|
local show_unsupported = G_reader_settings:isTrue("show_unsupported")
|
|
|
|
self.dirs = {self.path}
|
|
|
|
self.files = {}
|
|
|
|
while #self.dirs ~= 0 do
|
|
|
|
local new_dirs = {}
|
|
|
|
-- handle each dir
|
|
|
|
for __, d in pairs(self.dirs) do
|
|
|
|
-- handle files in d
|
|
|
|
local ok, iter, dir_obj = pcall(lfs.dir, d)
|
|
|
|
if ok then
|
|
|
|
for f in iter, dir_obj do
|
|
|
|
local fullpath = "/" .. f
|
|
|
|
if d ~= "/" then
|
|
|
|
fullpath = d .. fullpath
|
|
|
|
end
|
|
|
|
local attributes = lfs.attributes(fullpath) or {}
|
|
|
|
-- Don't traverse hidden folders if we're not showing them
|
|
|
|
if attributes.mode == "directory" and f ~= "." and f ~= ".."
|
|
|
|
and (G_reader_settings:isTrue("show_hidden") or not util.stringStartsWith(f, "."))
|
|
|
|
and FileChooser:show_dir(f)
|
|
|
|
then
|
|
|
|
if self.include_subfolders and not sys_folders[fullpath] then
|
|
|
|
table.insert(new_dirs, fullpath)
|
|
|
|
end
|
|
|
|
table.insert(self.files, {
|
|
|
|
dir = d,
|
|
|
|
name = f,
|
|
|
|
text = f.."/",
|
|
|
|
attr = attributes,
|
|
|
|
callback = function()
|
|
|
|
self:showFolder(fullpath .. "/")
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
-- Always ignore macOS resource forks, too.
|
|
|
|
elseif attributes.mode == "file" and not util.stringStartsWith(f, "._")
|
|
|
|
and (show_unsupported or DocumentRegistry:hasProvider(fullpath))
|
|
|
|
and FileChooser:show_file(f)
|
|
|
|
then
|
|
|
|
table.insert(self.files, {
|
|
|
|
dir = d,
|
|
|
|
name = f,
|
|
|
|
text = f,
|
|
|
|
mandatory = util.getFriendlySize(attributes.size or 0),
|
|
|
|
attr = attributes,
|
|
|
|
callback = function()
|
|
|
|
ReaderUI:showReader(fullpath)
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self.dirs = new_dirs
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function FileSearcher:setSearchResults()
|
|
|
|
local keywords = self.search_value
|
|
|
|
self.results = {}
|
|
|
|
if keywords == "*" then -- one * to show all files
|
|
|
|
self.results = self.files
|
|
|
|
else
|
|
|
|
if not self.case_sensitive then
|
|
|
|
keywords = Utf8Proc.lowercase(util.fixUtf8(keywords, "?"))
|
|
|
|
end
|
|
|
|
-- replace '.' with '%.'
|
|
|
|
keywords = keywords:gsub("%.","%%%.")
|
|
|
|
-- replace '*' with '.*'
|
|
|
|
keywords = keywords:gsub("%*","%.%*")
|
|
|
|
-- replace '?' with '.'
|
|
|
|
keywords = keywords:gsub("%?","%.")
|
|
|
|
for __,f in pairs(self.files) do
|
|
|
|
if self.case_sensitive then
|
|
|
|
if string.find(f.name, keywords) then
|
|
|
|
table.insert(self.results, f)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
if string.find(Utf8Proc.lowercase(util.fixUtf8(f.name, "?")), keywords) then
|
|
|
|
table.insert(self.results, f)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function FileSearcher:close()
|
|
|
|
UIManager:close(self.search_dialog)
|
|
|
|
self:readDir() --- @todo this probably doesn't need to be repeated once it's been done
|
|
|
|
self:setSearchResults() --- @todo doesn't have to be repeated if the search term is the same
|
|
|
|
if #self.results > 0 then
|
|
|
|
self:showSearchResults()
|
|
|
|
else
|
|
|
|
UIManager:show(
|
|
|
|
InfoMessage:new{
|
|
|
|
text = BaseUtil.template(_("No results for '%1'."),
|
|
|
|
self.search_value)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function FileSearcher:onShowFileSearch(search_string)
|
|
|
|
self.search_dialog = InputDialog:new{
|
|
|
|
title = _("Enter filename to search for"),
|
|
|
|
input = search_string or self.search_value,
|
|
|
|
buttons = {
|
|
|
|
{
|
|
|
|
{
|
|
|
|
text = _("Cancel"),
|
|
|
|
id = "close",
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(self.search_dialog)
|
|
|
|
end,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Home folder"),
|
|
|
|
enabled = G_reader_settings:has("home_dir"),
|
|
|
|
callback = function()
|
|
|
|
self.search_value = self.search_dialog:getInputText()
|
|
|
|
if self.search_value == "" then return end
|
|
|
|
self.path = G_reader_settings:readSetting("home_dir")
|
|
|
|
self:close()
|
|
|
|
end,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Current folder"),
|
|
|
|
is_enter_default = true,
|
|
|
|
callback = function()
|
|
|
|
self.search_value = self.search_dialog:getInputText()
|
|
|
|
if self.search_value == "" then return end
|
|
|
|
self.path = self.ui.file_chooser and self.ui.file_chooser.path or self.ui:getLastDirFile()
|
|
|
|
self:close()
|
|
|
|
end,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
self.check_button_case = CheckButton:new{
|
|
|
|
text = _("Case sensitive"),
|
|
|
|
checked = self.case_sensitive,
|
|
|
|
parent = self.search_dialog,
|
|
|
|
callback = function()
|
|
|
|
self.case_sensitive = self.check_button_case.checked
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
self.search_dialog:addWidget(self.check_button_case)
|
|
|
|
self.check_button_subfolders = CheckButton:new{
|
|
|
|
text = _("Include subfolders"),
|
|
|
|
checked = self.include_subfolders,
|
|
|
|
parent = self.search_dialog,
|
|
|
|
callback = function()
|
|
|
|
self.include_subfolders = self.check_button_subfolders.checked
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
self.search_dialog:addWidget(self.check_button_subfolders)
|
|
|
|
|
|
|
|
UIManager:show(self.search_dialog)
|
|
|
|
self.search_dialog:onShowKeyboard()
|
|
|
|
end
|
|
|
|
|
|
|
|
function FileSearcher:showSearchResults()
|
|
|
|
local menu_container = CenterContainer:new{
|
|
|
|
dimen = Screen:getSize(),
|
|
|
|
}
|
|
|
|
self.search_menu = Menu:new{
|
[RFC] Pagination UI shenanigans (#7335)
* Menu/KeyValuePage/ReaderGoTo: Unify the dialogs. (Generally, "Enter page number" as title, and "Go to page" as OK button).
* Allow *tapping* on pagination buttons, too. Added spacers around the text to accommodate for that.
* Disable input handlers when <= 1 pages, while still printing the label in black.
* Always display both the label and the chevrons, even on single page content. (Menu being an exception, because it can handle showing no content at all, in which case we hide the chevrons).
* KVP: Tweak the pagination buttons layout in order to have consistent centering, regardless of whether the return arrow is enabled or not. (Also, match Menu's layout, more or less).
* Menu: Minor layout tweaks to follow the KVP tweaks above. Fixes, among possibly other things, buttons in (non-FM) "List" menus overlapping the final entry (e.g., OPDS), and popout menus with a border being misaligned (e.g., Calibre, Find a file).
* CalendarView: Minor layout tweaks to follow the KVP tweaks. Ensures the pagination buttons are laid out in the same way as everywhere else (they used to be a wee bit higher).
4 years ago
|
|
|
width = Screen:getWidth() - (Size.margin.fullscreen_popout * 2),
|
|
|
|
height = Screen:getHeight() - (Size.margin.fullscreen_popout * 2),
|
|
|
|
show_parent = menu_container,
|
|
|
|
onMenuHold = self.onMenuHold,
|
|
|
|
_manager = self,
|
|
|
|
}
|
|
|
|
table.insert(menu_container, self.search_menu)
|
|
|
|
self.search_menu.close_callback = function()
|
|
|
|
UIManager:close(menu_container)
|
|
|
|
end
|
|
|
|
|
|
|
|
local collate = G_reader_settings:readSetting("collate") or "strcoll"
|
|
|
|
local reverse_collate = G_reader_settings:isTrue("reverse_collate")
|
|
|
|
local sorting = FileChooser:getSortingFunction(collate, reverse_collate)
|
|
|
|
|
|
|
|
table.sort(self.results, sorting)
|
|
|
|
self.search_menu:switchItemTable(T(_("Search results (%1)"), #self.results), self.results)
|
|
|
|
UIManager:show(menu_container)
|
|
|
|
end
|
|
|
|
|
|
|
|
function FileSearcher:onMenuHold(item)
|
|
|
|
local ReaderUI = require("apps/reader/readerui")
|
|
|
|
local is_file = item.attr.mode == "file"
|
|
|
|
local fullpath = item.dir .. "/" .. item.name .. (is_file and "" or "/")
|
|
|
|
local buttons = {
|
|
|
|
{
|
|
|
|
{
|
|
|
|
text = _("Cancel"),
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(self.results_dialog)
|
|
|
|
end,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Show folder"),
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(self.results_dialog)
|
|
|
|
self.close_callback()
|
|
|
|
self._manager:showFolder(fullpath)
|
|
|
|
end,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
if is_file then
|
|
|
|
table.insert(buttons[1], {
|
|
|
|
text = _("Open"),
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(self.results_dialog)
|
|
|
|
self.close_callback()
|
|
|
|
ReaderUI:showReader(fullpath)
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
|
|
|
self.results_dialog = ButtonDialogTitle:new{
|
|
|
|
title = fullpath,
|
|
|
|
buttons = buttons,
|
|
|
|
}
|
|
|
|
UIManager:show(self.results_dialog)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function FileSearcher:showFolder(path)
|
|
|
|
if self.ui.file_chooser then
|
|
|
|
local pathname = util.splitFilePathName(path)
|
|
|
|
self.ui.file_chooser:changeToPath(pathname, path)
|
|
|
|
else -- called from Reader
|
|
|
|
self.ui:onClose()
|
|
|
|
self.ui:showFileManager(path)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return FileSearcher
|