Open with... improvement (#11056)

Allows associating filetypes with non-document providers (like ImageViewer or TextViewer) to "open" (view) these files by a tap in file browser.
reviewable/pr11079/r1
hius07 6 months ago committed by GitHub
parent 94a82087de
commit 68aa209a6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -166,8 +166,7 @@ function FileManager:setupLayout()
end
self:refreshPath()
else
local ReaderUI = require("apps/reader/readerui")
ReaderUI:showReader(file)
file_manager:openFile(file)
end
return true
end
@ -268,43 +267,7 @@ function FileManager:setupLayout()
text = _("Open with…"),
callback = function()
UIManager:close(self.file_dialog)
local one_time_providers = {}
if DocumentRegistry:isImageFile(file) then
table.insert(one_time_providers, {
provider_name = _("Image viewer"),
callback = function()
local ImageViewer = require("ui/widget/imageviewer")
UIManager:show(ImageViewer:new{
file = file,
fullscreen = true,
with_title_bar = false,
})
end,
})
end
table.insert(one_time_providers, {
provider_name = _("Text viewer"),
callback = function()
file_manager:openTextViewer(file)
end,
})
if file_manager.texteditor then
table.insert(one_time_providers, {
provider_name = _("Text editor"),
callback = function()
file_manager.texteditor:checkEditFile(file)
end,
})
end
if file_manager.archiveviewer and file_manager.archiveviewer:isSupported(file) then
table.insert(one_time_providers, {
provider_name = _("Archive viewer"),
callback = function()
file_manager.archiveviewer:openArchiveViewer(file)
end,
})
end
self:showSetProviderButtons(file, one_time_providers)
file_manager:showOpenWithDialog(file)
end,
},
filemanagerutil.genBookInformationButton(file, close_dialog_callback),
@ -410,7 +373,7 @@ function FileManager:init()
self.active_widgets = {}
self:registerModule("screenshot", Screenshoter:new{
prefix = 'FileManager',
prefix = "FileManager",
ui = self,
}, true)
@ -838,7 +801,10 @@ function FileManager:setHome(path)
end
function FileManager:openRandomFile(dir)
local random_file = DocumentRegistry:getRandomFile(dir, false)
local match_func = function(file) -- documents, not yet opened
return DocumentRegistry:hasProvider(file) and not DocSettings:hasSidecarFile(file)
end
local random_file = filemanagerutil.getRandomFile(dir, match_func)
if random_file then
UIManager:show(MultiConfirmBox:new{
text = T(_("Do you want to open %1?"), BD.filename(BaseUtil.basename(random_file))),
@ -1139,36 +1105,6 @@ function FileManager:showFiles(path, focused_file)
UIManager:show(file_manager)
end
function FileManager:openTextViewer(file_path)
local function _openTextViewer(filepath)
local file = io.open(filepath, "rb")
if not file then return end
local file_content = file:read("*all")
file:close()
UIManager:show(require("ui/widget/textviewer"):new{
title = filepath,
title_multilines = true,
justified = false,
text = file_content,
})
end
local attr = lfs.attributes(file_path)
if attr then
if attr.size > 400000 then
UIManager:show(ConfirmBox:new{
text = T(_("This file is %2:\n\n%1\n\nAre you sure you want to open it?\n\nOpening big files may take some time."),
BD.filepath(file_path), util.getFriendlySize(attr.size)),
ok_text = _("Open"),
ok_callback = function()
_openTextViewer(file_path)
end,
})
else
_openTextViewer(file_path)
end
end
end
--- A shortcut to execute mv.
-- @treturn boolean result of mv command
function FileManager:moveFile(from, to)
@ -1304,4 +1240,168 @@ function FileManager:showSelectedFilesList()
UIManager:show(menu_container)
end
function FileManager:showOpenWithDialog(file)
local file_associated_provider_key = DocumentRegistry:getAssociatedProviderKey(file, false)
local type_associated_provider_key = DocumentRegistry:getAssociatedProviderKey(file, true)
local file_provider_key = file_associated_provider_key
or type_associated_provider_key
or DocumentRegistry:getProvider(file).provider
-- radio buttons (all providers)
local function genRadioButton(provider, is_unsupported)
return {{
-- @translators %1 is the provider name, such as Cool Reader Engine or MuPDF.
text = is_unsupported and T(_("%1 ~Unsupported"), provider.provider_name) or provider.provider_name,
checked = provider.provider == file_provider_key,
provider = provider,
}}
end
local radio_buttons = {}
local providers = DocumentRegistry:getProviders(file) -- document providers
if providers then
for _, provider in ipairs(providers) do
table.insert(radio_buttons, genRadioButton(provider.provider))
end
else
local provider = DocumentRegistry:getFallbackProvider()
table.insert(radio_buttons, genRadioButton(provider, true))
end
for _, provider in ipairs(DocumentRegistry:getAuxProviders()) do -- auxiliary providers
local is_filetype_supported
if provider.enabled_func then -- module
is_filetype_supported = provider.enabled_func(file)
else -- plugin
is_filetype_supported = self[provider.provider]:isFileTypeSupported(file)
end
if is_filetype_supported then
table.insert(radio_buttons, genRadioButton(provider))
end
end
-- buttons
local __, filename_pure = util.splitFilePathName(file)
filename_pure = BD.filename(filename_pure)
local filename_suffix = util.getFileNameSuffix(file):lower()
local dialog
local buttons = {}
-- row: wide button
if file_associated_provider_key then
table.insert(buttons, {{
text = _("Reset default for this file"),
callback = function()
DocumentRegistry:setProvider(file, nil, false)
UIManager:close(dialog)
end,
}})
end
-- row: wide button
if type_associated_provider_key then
table.insert(buttons, {{
text = T(_("Reset default for %1 files"), filename_suffix),
callback = function()
DocumentRegistry:setProvider(file, nil, true)
UIManager:close(dialog)
end,
}})
end
-- row: wide button
local associated_providers = DocumentRegistry:getAssociatedProviderKey() -- hash table
if next(associated_providers) ~= nil then
table.insert(buttons, {{
text = _("View defaults for file types"),
callback = function()
local max_len = 0 -- align extensions
for extension in pairs(associated_providers) do
if max_len < #extension then
max_len = #extension
end
end
local t = {}
for extension, provider_key in BaseUtil.orderedPairs(associated_providers) do
local provider = DocumentRegistry:getProviderFromKey(provider_key)
if provider then
local space = string.rep(" ", max_len - #extension)
table.insert(t, T("%1%2: %3", extension, space, provider.provider_name))
end
end
UIManager:show(InfoMessage:new{
text = table.concat(t, "\n"),
monospace_font = true,
})
end,
}})
end
-- row: 2 buttons
table.insert(buttons, {
{
text = _("Cancel"),
callback = function()
UIManager:close(dialog)
end,
},
{
text = _("Open"),
is_enter_default = true,
callback = function()
local provider = dialog.radio_button_table.checked_button.provider
if dialog._check_file_button.checked then -- set this file associated provider
UIManager:show(ConfirmBox:new{
text = T(_("Always open '%2' with %1?"), provider.provider_name, filename_pure),
ok_text = _("Always"),
ok_callback = function()
DocumentRegistry:setProvider(file, provider, false)
self:openFile(file, provider)
UIManager:close(dialog)
end,
})
elseif dialog._check_global_button.checked then -- set file type associated provider
UIManager:show(ConfirmBox:new{
text = T(_("Always open %2 files with %1?"), provider.provider_name, filename_suffix),
ok_text = _("Always"),
ok_callback = function()
DocumentRegistry:setProvider(file, provider, true)
self:openFile(file, provider)
UIManager:close(dialog)
end,
})
else -- open just once
self:openFile(file, provider)
UIManager:close(dialog)
end
end,
},
})
local OpenWithDialog = require("ui/widget/openwithdialog")
dialog = OpenWithDialog:new{
title = T(_("Open %1 with:"), filename_pure),
radio_buttons = radio_buttons,
buttons = buttons,
}
UIManager:show(dialog)
end
function FileManager:openFile(file, provider, doc_caller_callback, aux_caller_callback)
if not provider then -- check associated
local provider_key = DocumentRegistry:getAssociatedProviderKey(file)
provider = provider_key and DocumentRegistry:getProviderFromKey(provider_key)
end
if provider and provider.order then -- auxiliary
if aux_caller_callback then
aux_caller_callback()
end
if provider.callback then -- module
provider.callback(file)
else -- plugin
self[provider.provider]:openFile(file)
end
else -- document
if doc_caller_callback then
doc_caller_callback()
end
local ReaderUI = require("apps/reader/readerui")
ReaderUI:showReader(file, provider)
end
end
return FileManager

@ -161,7 +161,7 @@ function FileManagerCollection:showCollDialog()
path = G_reader_settings:readSetting("home_dir"),
select_directory = false,
file_filter = function(file)
return DocumentRegistry:getProviders(file) ~= nil
return DocumentRegistry:hasProvider(file)
end,
onConfirm = function(file)
if not ReadCollection:checkItemExist(file) then

@ -269,7 +269,6 @@ end
function FileSearcher:onMenuSelect(item)
local file = item.path
local has_provider = false
local dialog
local function close_dialog_callback()
UIManager:close(dialog)
@ -281,8 +280,7 @@ function FileSearcher:onMenuSelect(item)
local buttons = {}
if item.is_file then
local is_currently_opened = self.ui.document and self.ui.document.file == file
has_provider = DocumentRegistry:hasProvider(file)
if has_provider or DocSettings:hasSidecarFile(file) then
if DocumentRegistry:hasProvider(file) or DocSettings:hasSidecarFile(file) then
local doc_settings_or_file = is_currently_opened and self.ui.doc_settings or file
table.insert(buttons, filemanagerutil.genStatusButtonsRow(doc_settings_or_file, close_dialog_callback))
table.insert(buttons, {}) -- separator
@ -317,11 +315,11 @@ function FileSearcher:onMenuSelect(item)
filemanagerutil.genShowFolderButton(file, close_dialog_menu_callback),
{
text = _("Open"),
enabled = has_provider,
enabled = DocumentRegistry:hasProvider(file, nil, true), -- allow auxiliary providers
callback = function()
close_dialog_menu_callback()
local ReaderUI = require("apps/reader/readerui")
ReaderUI:showReader(file)
close_dialog_callback()
local FileManager = require("apps/filemanager/filemanager")
FileManager.openFile(self.ui, file, nil, self.close_callback)
end,
},
})
@ -334,10 +332,9 @@ end
function FileSearcher:onMenuHold(item)
if item.is_file then
if DocumentRegistry:hasProvider(item.path) then
self.close_callback()
local ReaderUI = require("apps/reader/readerui")
ReaderUI:showReader(item.path)
if DocumentRegistry:hasProvider(item.path, nil, true) then
local FileManager = require("apps/filemanager/filemanager")
FileManager.openFile(self.ui, item.path, nil, self.close_callback)
end
else
self.close_callback()

@ -8,6 +8,7 @@ local DocSettings = require("docsettings")
local Event = require("ui/event")
local UIManager = require("ui/uimanager")
local ffiutil = require("ffi/util")
local lfs = require("libs/libkoreader-lfs")
local util = require("util")
local _ = require("gettext")
local T = ffiutil.template
@ -49,6 +50,26 @@ function filemanagerutil.splitFileNameType(filepath)
return filename_without_suffix, filetype
end
function filemanagerutil.getRandomFile(dir, match_func)
if not dir:match("/$") then
dir = dir .. "/"
end
local files = {}
local ok, iter, dir_obj = pcall(lfs.dir, dir)
if ok then
for entry in iter, dir_obj do
local file = dir .. entry
if lfs.attributes(file, "mode") == "file" and match_func(file) then
table.insert(files, entry)
end
end
if #files > 0 then
math.randomseed(os.time())
return dir .. files[math.random(#files)]
end
end
end
-- Purge doc settings except kept
function filemanagerutil.resetDocumentSettings(file)
local settings_to_keep = {

@ -6,6 +6,7 @@ local BD = require("ui/bidi")
local ButtonDialogTitle = require("ui/widget/buttondialogtitle")
local ConfirmBox = require("ui/widget/confirmbox")
local Device = require("device")
local DocumentRegistry = require("document/documentregistry")
local Event = require("ui/event")
local InfoMessage = require("ui/widget/infomessage")
local InputContainer = require("ui/widget/container/inputcontainer")
@ -822,9 +823,7 @@ function ReaderLink:onGotoLink(link, neglect_current_location, allow_footnote_po
linked_filename = ffiutil.joinPath(self.document_dir, linked_filename) -- get full path
linked_filename = ffiutil.realpath(linked_filename) -- clean full path from ./ or ../
if linked_filename and lfs.attributes(linked_filename, "mode") == "file" then
local DocumentRegistry = require("document/documentregistry")
local provider = DocumentRegistry:getProvider(linked_filename)
if provider then
if DocumentRegistry:hasProvider(linked_filename) then
-- Display filename with anchor or query string, so the user gets
-- this information and can manually go to the appropriate place
local display_filename = linked_filename

@ -4,12 +4,12 @@ This is a registry for document providers
local DocSettings = require("docsettings")
local logger = require("logger")
local lfs = require("libs/libkoreader-lfs")
local util = require("util")
local DocumentRegistry = {
registry = {},
providers = {},
known_providers = {}, -- hash table of registered providers { provider_key = provider }
filetype_provider = {},
mimetype_ext = {},
image_ext = {
@ -24,6 +24,10 @@ local DocumentRegistry = {
},
}
local function getSuffix(file)
return util.getFileNameSuffix(file):lower()
end
function DocumentRegistry:addProvider(extension, mimetype, provider, weight)
extension = string.lower(extension)
table.insert(self.providers, {
@ -37,97 +41,65 @@ function DocumentRegistry:addProvider(extension, mimetype, provider, weight)
-- Provided we order the calls to addProvider() correctly,
-- that means epub instead of epub3, etc.
self.mimetype_ext[mimetype] = self.mimetype_ext[mimetype] or extension
if self.known_providers[provider.provider] == nil then
self.known_providers[provider.provider] = provider
end
end
function DocumentRegistry:getRandomFile(dir, opened, extension)
if dir:sub(-1) ~= "/" then
dir = dir .. "/"
end
local files = {}
local i = 0
local ok, iter, dir_obj = pcall(lfs.dir, dir)
if ok then
for entry in iter, dir_obj do
local file = dir .. entry
local file_opened = DocSettings:hasSidecarFile(file)
if lfs.attributes(file, "mode") == "file" and self:hasProvider(file)
and (opened == nil or file_opened == opened)
and (extension == nil or extension[util.getFileNameSuffix(entry)]) then
i = i + 1
files[i] = entry
end
end
if i == 0 then
return nil
end
else
return nil
end
math.randomseed(os.time())
return dir .. files[math.random(i)]
-- Register an auxiliary (non-document) provider.
-- Aux providers are modules (eg TextViewer) or plugins (eg TextEditor).
-- It does not implement the Document API.
-- For plugins the hash table value does not contain file handler,
-- but only a provider_key (provider.provider) to call the corresponding
-- plugin in FileManager:openFile().
function DocumentRegistry:addAuxProvider(provider)
self.known_providers[provider.provider] = provider
end
--- Returns true if file has provider.
-- @string file
-- @bool include_aux include auxiliary (non-document) providers
-- @treturn boolean
function DocumentRegistry:hasProvider(file, mimetype)
function DocumentRegistry:hasProvider(file, mimetype, include_aux)
if mimetype and self.mimetype_ext[mimetype] then
return true
end
if not file then return false end
local filename_suffix = string.lower(util.getFileNameSuffix(file))
local filetype_provider = G_reader_settings:readSetting("provider") or {}
if self.filetype_provider[filename_suffix] or filetype_provider[filename_suffix] then
-- registered document provider
local filename_suffix = getSuffix(file)
if self.filetype_provider[filename_suffix] then
return true
end
-- associated document or auxiliary provider for file type
local filetype_provider_key = G_reader_settings:readSetting("provider", {})[filename_suffix]
local provider = filetype_provider_key and self.known_providers[filetype_provider_key]
if provider and (not provider.order or include_aux) then -- excluding auxiliary by default
return true
end
-- associated document provider for this file
if DocSettings:hasSidecarFile(file) then
return DocSettings:open(file):has("provider")
end
return false
end
--- Returns the preferred registered document handler.
--- Returns the preferred registered document handler or fallback provider.
-- @string file
-- @treturn table provider, or nil
-- @treturn table provider
function DocumentRegistry:getProvider(file)
local providers = self:getProviders(file)
if providers then
-- provider for document
if DocSettings:hasSidecarFile(file) then
local doc_settings_provider = DocSettings:open(file):readSetting("provider")
if doc_settings_provider then
for _, provider in ipairs(providers) do
if provider.provider.provider == doc_settings_provider then
return provider.provider
end
end
end
-- associated provider
local provider_key = DocumentRegistry:getAssociatedProviderKey(file)
local provider = provider_key and self.known_providers[provider_key]
if provider and not provider.order then -- excluding auxiliary
return provider
end
-- global provider for filetype
local filename_suffix = util.getFileNameSuffix(file)
local g_settings_provider = G_reader_settings:readSetting("provider")
if g_settings_provider and g_settings_provider[filename_suffix] then
for _, provider in ipairs(providers) do
if provider.provider.provider == g_settings_provider[filename_suffix] then
return provider.provider
end
end
end
-- highest weighted provider
return providers[1].provider
else
for _, provider in ipairs(self.providers) do
if provider.extension == "txt" then
return provider.provider
end
end
end
return self:getFallbackProvider()
end
--- Returns the registered document handlers.
@ -166,6 +138,59 @@ function DocumentRegistry:getProviders(file)
end
end
function DocumentRegistry:getProviderFromKey(provider_key)
return self.known_providers[provider_key]
end
function DocumentRegistry:getFallbackProvider()
for _, provider in ipairs(self.providers) do
if provider.extension == "txt" then
return provider.provider
end
end
end
function DocumentRegistry:getAssociatedProviderKey(file, all)
-- all: nil - first not empty, false - this file, true - file type
if not file then -- get the full list of associated providers
return G_reader_settings:readSetting("provider")
end
-- provider for this file
local provider_key
if all ~= true then
if DocSettings:hasSidecarFile(file) then
provider_key = DocSettings:open(file):readSetting("provider")
if provider_key or all == false then
return provider_key
end
end
if all == false then return end
end
-- provider for file type
local providers = G_reader_settings:readSetting("provider")
provider_key = providers and providers[getSuffix(file)]
if provider_key and self.known_providers[provider_key] then
return provider_key
end
end
-- Returns array: registered auxiliary providers sorted by order.
function DocumentRegistry:getAuxProviders()
local providers = {}
for _, provider in pairs(self.known_providers) do
if provider.order then -- aux
table.insert(providers, provider)
end
end
if #providers >= 1 then
table.sort(providers, function(a, b) return a.order < b.order end)
return providers
end
end
--- Get mapping of file extensions to providers
-- @treturn table mapping file extensions to a list of providers
function DocumentRegistry:getExtensions()
@ -182,8 +207,7 @@ end
-- @string file
-- @bool all
function DocumentRegistry:setProvider(file, provider, all)
local _, filename_suffix = util.splitFileNameSuffix(file)
provider = provider or {} -- call with nil to reset
-- per-document
if not all then
local doc_settings = DocSettings:open(file)
@ -191,9 +215,8 @@ function DocumentRegistry:setProvider(file, provider, all)
doc_settings:flush()
-- global
else
local filetype_provider = G_reader_settings:readSetting("provider") or {}
filetype_provider[filename_suffix] = provider.provider
G_reader_settings:saveSetting("provider", filetype_provider)
local filetype_provider = G_reader_settings:readSetting("provider", {})
filetype_provider[getSuffix(file)] = provider.provider
end
end
@ -257,7 +280,7 @@ function DocumentRegistry:getReferenceCount(file)
end
function DocumentRegistry:isImageFile(file)
return self.image_ext[util.getFileNameSuffix(file):lower()] and true or false
return self.image_ext[getSuffix(file)] and true or false
end
-- load implementations:
@ -265,5 +288,9 @@ require("document/credocument"):register(DocumentRegistry)
require("document/pdfdocument"):register(DocumentRegistry)
require("document/djvudocument"):register(DocumentRegistry)
require("document/picdocument"):register(DocumentRegistry)
-- auxiliary built-in
require("ui/widget/imageviewer"):register(DocumentRegistry)
require("ui/widget/textviewer"):register(DocumentRegistry)
-- auxiliary from plugins
return DocumentRegistry

@ -23,6 +23,7 @@ local VerticalGroup = require("ui/widget/verticalgroup")
local VerticalSpan = require("ui/widget/verticalspan")
local datetime = require("datetime")
local ffiUtil = require("ffi/util")
local filemanagerutil = require("apps/filemanager/filemanagerutil")
local lfs = require("libs/libkoreader-lfs")
local logger = require("logger")
local util = require("util")
@ -78,35 +79,12 @@ if Device:isEmulator() then
Screensaver.default_screensaver_message = Screensaver.default_screensaver_message .. "\n" .. _("(Press F2 to resume)")
end
function Screensaver:_getRandomImage(dir)
if not dir then
return nil
local function _getRandomImage(dir)
if not dir then return end
local match_func = function(file) -- images, ignore macOS resource forks
return not util.stringStartsWith(file, "._") and DocumentRegistry:isImageFile(file)
end
if string.sub(dir, string.len(dir)) ~= "/" then
dir = dir .. "/"
end
local pics = {}
local i = 0
math.randomseed(os.time())
local ok, iter, dir_obj = pcall(lfs.dir, dir)
if ok then
for f in iter, dir_obj do
-- Always ignore macOS resource forks, too.
if lfs.attributes(dir .. f, "mode") == "file" and not util.stringStartsWith(f, "._")
and DocumentRegistry:isImageFile(f) then
i = i + 1
pics[i] = f
end
end
if i == 0 then
return nil
end
else
return nil
end
return dir .. pics[math.random(i)]
return filemanagerutil.getRandomFile(dir, match_func)
end
-- This is implemented by the Statistics plugin
@ -573,7 +551,7 @@ function Screensaver:setup(event, event_message)
if self.screensaver_type == "random_image" then
local screensaver_dir = G_reader_settings:readSetting(self.prefix .. "screensaver_dir")
or G_reader_settings:readSetting("screensaver_dir")
self.image_file = self:_getRandomImage(screensaver_dir) or "resources/koreader.png" -- Fallback image
self.image_file = _getRandomImage(screensaver_dir) or "resources/koreader.png" -- Fallback image
end
-- Use the right background setting depending on the effective mode, now that fallbacks have kicked in.

@ -1,12 +1,10 @@
local BD = require("ui/bidi")
local ConfirmBox = require("ui/widget/confirmbox")
local datetime = require("datetime")
local Device = require("device")
local DocSettings = require("docsettings")
local DocumentRegistry = require("document/documentregistry")
local filemanagerutil = require("apps/filemanager/filemanagerutil")
local Menu = require("ui/widget/menu")
local OpenWithDialog = require("ui/widget/openwithdialog")
local UIManager = require("ui/uimanager")
local ffi = require("ffi")
local ffiUtil = require("ffi/util")
@ -515,121 +513,4 @@ function FileChooser:selectAllFilesInFolder()
end
end
function FileChooser:showSetProviderButtons(file, one_time_providers)
local ReaderUI = require("apps/reader/readerui")
local __, filename_pure = util.splitFilePathName(file)
local filename_suffix = util.getFileNameSuffix(file)
local buttons = {}
local radio_buttons = {}
local filetype_provider = G_reader_settings:readSetting("provider") or {}
local providers = DocumentRegistry:getProviders(file)
if providers ~= nil then
for ___, provider in ipairs(providers) do
-- we have no need for extension, mimetype, weights, etc. here
provider = provider.provider
table.insert(radio_buttons, {
{
text = provider.provider_name,
checked = DocumentRegistry:getProvider(file) == provider,
provider = provider,
},
})
end
else
local provider = DocumentRegistry:getProvider(file)
table.insert(radio_buttons, {
{
-- @translators %1 is the provider name, such as Cool Reader Engine or MuPDF.
text = T(_("%1 ~Unsupported"), provider.provider_name),
checked = true,
provider = provider,
},
})
end
for _, provider in ipairs(one_time_providers) do
provider.one_time_provider = true
table.insert(radio_buttons, {
{
text = provider.provider_name,
provider = provider,
},
})
end
table.insert(buttons, {
{
text = _("Cancel"),
callback = function()
UIManager:close(self.set_provider_dialog)
end,
},
{
text = _("Open"),
is_enter_default = true,
callback = function()
local provider = self.set_provider_dialog.radio_button_table.checked_button.provider
if provider.one_time_provider then
UIManager:close(self.set_provider_dialog)
provider.callback()
return
end
-- always for this file
if self.set_provider_dialog._check_file_button.checked then
UIManager:show(ConfirmBox:new{
text = T(_("Always open '%2' with %1?"),
provider.provider_name, BD.filename(filename_pure)),
ok_text = _("Always"),
ok_callback = function()
DocumentRegistry:setProvider(file, provider, false)
ReaderUI:showReader(file, provider)
UIManager:close(self.set_provider_dialog)
end,
})
-- always for all files of this file type
elseif self.set_provider_dialog._check_global_button.checked then
UIManager:show(ConfirmBox:new{
text = T(_("Always open %2 files with %1?"),
provider.provider_name, filename_suffix),
ok_text = _("Always"),
ok_callback = function()
DocumentRegistry:setProvider(file, provider, true)
ReaderUI:showReader(file, provider)
UIManager:close(self.set_provider_dialog)
end,
})
else
-- just once
ReaderUI:showReader(file, provider)
UIManager:close(self.set_provider_dialog)
end
end,
},
})
if filetype_provider[filename_suffix] ~= nil then
table.insert(buttons, {
{
text = _("Reset default"),
callback = function()
filetype_provider[filename_suffix] = nil
G_reader_settings:saveSetting("provider", filetype_provider)
UIManager:close(self.set_provider_dialog)
end,
},
})
end
self.set_provider_dialog = OpenWithDialog:new{
title = T(_("Open %1 with:"), BD.filename(filename_pure)),
radio_buttons = radio_buttons,
buttons = buttons,
}
UIManager:show(self.set_provider_dialog)
end
return FileChooser

@ -851,4 +851,27 @@ function ImageViewer:onCloseWidget()
end)
end
-- Register DocumentRegistry auxiliary provider.
function ImageViewer:register(registry)
registry:addAuxProvider({
provider_name = _("Image viewer"),
provider = "imageviewer",
order = 10, -- order in OpenWith dialog
enabled_func = function(file)
return registry:isImageFile(file)
end,
callback = ImageViewer.openFile,
disable_file = true,
disable_type = false,
})
end
function ImageViewer.openFile(file)
UIManager:show(ImageViewer:new{
file = file,
fullscreen = true,
with_title_bar = false,
})
end
return ImageViewer

@ -22,7 +22,6 @@ Show image from memory example:
local Blitbuffer = require("ffi/blitbuffer")
local Cache = require("cache")
local DocumentRegistry = require("document/documentregistry")
local Geom = require("ui/geometry")
local RenderImage = require("ui/renderimage")
local Screen = require("device").screen
@ -131,6 +130,7 @@ function ImageWidget:_loadimage()
end
function ImageWidget:_loadfile()
local DocumentRegistry = require("document/documentregistry")
if DocumentRegistry:isImageFile(self.file) then
-- In our use cases for files (icons), we either provide width and height,
-- or just scale_for_dpi, and scale_factor should stay nil.

@ -46,7 +46,8 @@ local Screen = Device.screen
local InfoMessage = InputContainer:extend{
modal = true,
face = Font:getFace("infofont"),
face = nil,
monospace_font = false,
text = "",
timeout = nil, -- in seconds
width = nil, -- The width of the InfoMessage. Keep it nil to use default value.
@ -77,6 +78,10 @@ local InfoMessage = InputContainer:extend{
}
function InfoMessage:init()
if not self.face then
self.face = Font:getFace(self.monospace_font and "infont" or "infofont")
end
if self.dismissable then
if Device:hasKeys() then
self.key_events.AnyKeyPressed = { { Input.group.Any } }

@ -32,11 +32,14 @@ function OpenWithDialog:init()
scroll = false,
parent = self,
button_select_callback = function(btn)
if btn.provider.one_time_provider then
if btn.provider.disable_file then
self._check_file_button:disable()
self._check_global_button:disable()
else
self._check_file_button:enable()
end
if btn.provider.disable_type then
self._check_global_button:disable()
else
self._check_global_button:enable()
end
end
@ -83,11 +86,13 @@ function OpenWithDialog:init()
self._check_file_button = self._check_file_button or CheckButton:new{
text = _("Always use this engine for this file"),
enabled = not self.radio_button_table.checked_button.provider.disable_file,
parent = self,
}
self:addWidget(self._check_file_button)
self._check_global_button = self._check_global_button or CheckButton:new{
text = _("Always use this engine for file type"),
enabled = not self.radio_button_table.checked_button.provider.disable_type,
parent = self,
}
self:addWidget(self._check_global_button)

@ -29,6 +29,7 @@ local UIManager = require("ui/uimanager")
local VerticalGroup = require("ui/widget/verticalgroup")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local T = require("ffi/util").template
local lfs = require("libs/libkoreader-lfs")
local util = require("util")
local _ = require("gettext")
local Screen = Device.screen
@ -431,7 +432,6 @@ function TextViewer:onForwardingPanRelease(arg, ges)
return self.movable:onMovablePanRelease(arg, ges)
end
function TextViewer:findDialog()
local input_dialog
input_dialog = InputDialog:new{
@ -530,4 +530,50 @@ function TextViewer:handleTextSelection(text, hold_duration, start_idx, end_idx,
end
end
-- Register DocumentRegistry auxiliary provider.
function TextViewer:register(registry)
registry:addAuxProvider({
provider_name = _("Text viewer"),
provider = "textviewer",
order = 20, -- order in OpenWith dialog
enabled_func = function()
return true -- all files
end,
callback = TextViewer.openFile,
disable_file = true,
disable_type = false,
})
end
function TextViewer.openFile(file)
local function _openFile(file_path)
local file_handle = io.open(file_path, "rb")
if not file_handle then return end
local file_content = file_handle:read("*all")
file_handle:close()
UIManager:show(TextViewer:new{
title = file_path,
title_multilines = true,
justified = false,
text = file_content,
})
end
local attr = lfs.attributes(file)
if attr then
if attr.size > 400000 then
local ConfirmBox = require("ui/widget/confirmbox")
UIManager:show(ConfirmBox:new{
text = T(_("This file is %2:\n\n%1\n\nAre you sure you want to open it?\n\nOpening big files may take some time."),
BD.filepath(file), util.getFriendlySize(attr.size)),
ok_text = _("Open"),
ok_callback = function()
_openFile(file)
end,
})
else
_openFile(file)
end
end
end
return TextViewer

@ -2,8 +2,10 @@ local BD = require("ui/bidi")
local ButtonDialog = require("ui/widget/buttondialog")
local CenterContainer = require("ui/widget/container/centercontainer")
local DocumentRegistry = require("document/documentregistry")
local InfoMessage = require("ui/widget/infomessage")
local ImageViewer = require("ui/widget/imageviewer")
local Menu = require("ui/widget/menu")
local RenderImage = require("ui/renderimage")
local TextViewer = require("ui/widget/textviewer")
local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local ffiUtil = require("ffi/util")
@ -13,6 +15,9 @@ local Screen = require("device").screen
local T = ffiUtil.template
local ArchiveViewer = WidgetContainer:extend{
name = "archiveviewer",
fullname = _("Archive viewer"),
arc_file = nil, -- archive
-- list_table is a flat table containing archive files and folders
-- key - a full path of the folder ("/" for root), for all folders and subfolders of any level
-- value - a subtable of subfolders and files in the folder
@ -27,21 +32,44 @@ local ArchiveViewer = WidgetContainer:extend{
},
}
function ArchiveViewer:isSupported(file)
return self.arc_ext[util.getFileNameSuffix(file):lower()] and true or false
local ZIP_LIST = "unzip -qql \"%1\""
local ZIP_EXTRACT_CONTENT = "unzip -qqp \"%1\" \"%2\""
local ZIP_EXTRACT_FILE = "unzip -qqo \"%1\" \"%2\" -d \"%3\"" -- overwrite
local function getSuffix(file)
return util.getFileNameSuffix(file):lower()
end
function ArchiveViewer:init()
self:registerDocumentRegistryAuxProvider()
end
function ArchiveViewer:registerDocumentRegistryAuxProvider()
DocumentRegistry:addAuxProvider({
provider_name = self.fullname,
provider = self.name,
order = 40, -- order in OpenWith dialog
disable_file = true,
disable_type = false,
})
end
function ArchiveViewer:openArchiveViewer(file)
function ArchiveViewer:isFileTypeSupported(file)
return self.arc_ext[getSuffix(file)] and true or false
end
function ArchiveViewer:openFile(file)
local _, filename = util.splitFilePathName(file)
local fileext = util.getFileNameSuffix(file):lower()
local fileext = getSuffix(filename)
if fileext == "cbz" or fileext == "epub" or fileext == "zip" then
self.arc_type = "zip"
end
self.arc_file = file
self.fm_updated = nil
self.list_table = {}
if self.arc_type == "zip" then
self:getZipListTable(file)
self:getZipListTable()
else -- add other archivers here
return
end
@ -50,17 +78,17 @@ function ArchiveViewer:openArchiveViewer(file)
local menu_container = CenterContainer:new{
dimen = Screen:getSize(),
}
local menu = Menu:new{
self.menu = Menu:new{
is_borderless = true,
is_popout = false,
title_multilines = true,
show_parent = menu_container,
onMenuSelect = function(self_menu, item)
if item.text:sub(-1) == "/" then -- folder
if item.is_file then
self:showFileDialog(item.path)
else
local title = item.path == "" and filename or filename.."/"..item.path
self_menu:switchItemTable(title, self:getItemTable(item.path))
else
self:showFileDialog(file, item.path)
end
end,
close_callback = function()
@ -70,12 +98,12 @@ function ArchiveViewer:openArchiveViewer(file)
end
end,
}
table.insert(menu_container, menu)
menu:switchItemTable(filename, item_table)
table.insert(menu_container, self.menu)
self.menu:switchItemTable(filename, item_table)
UIManager:show(menu_container)
end
function ArchiveViewer:getZipListTable(file)
function ArchiveViewer:getZipListTable()
local function parse_path(filepath, filesize)
if not filepath then return end
local path, name = util.splitFilePathName(filepath)
@ -97,7 +125,7 @@ function ArchiveViewer:getZipListTable(file)
end
end
local std_out = io.popen("unzip ".."-qql \""..file.."\"")
local std_out = io.popen(T(ZIP_LIST, self.arc_file))
if std_out then
for line in std_out:lines() do
-- entry datetime not used so far
@ -129,6 +157,7 @@ function ArchiveViewer:getItemTable(path)
if v then -- file
table.insert(files, {
text = name,
is_file = true,
bidi_wrap_func = BD.filename,
path = prefix..name,
mandatory = util.getFriendlySize(tonumber(v)),
@ -173,7 +202,7 @@ function ArchiveViewer:getItemDirMandatory(name)
return text
end
function ArchiveViewer:showFileDialog(arcfile, filepath)
function ArchiveViewer:showFileDialog(filepath)
local dialog
local buttons = {
{
@ -181,14 +210,14 @@ function ArchiveViewer:showFileDialog(arcfile, filepath)
text = _("Extract"),
callback = function()
UIManager:close(dialog)
self:extractFile(arcfile, filepath)
self:extractFile(filepath)
end,
},
{
text = _("View"),
callback = function()
UIManager:close(dialog)
self:viewFile(arcfile, filepath)
self:viewFile(filepath)
end,
},
},
@ -201,47 +230,53 @@ function ArchiveViewer:showFileDialog(arcfile, filepath)
UIManager:show(dialog)
end
function ArchiveViewer:viewFile(arcfile, filepath)
local content
if self.arc_type == "zip" then
local std_out = io.popen("unzip ".."-qp \""..arcfile.."\"".." ".."\""..filepath.."\"")
if std_out then
content = std_out:read("*all")
std_out:close()
else
UIManager:show(InfoMessage:new{
text = _("Cannot extract file content."),
icon = "notice-warning",
})
return
end
else
return
end
function ArchiveViewer:viewFile(filepath)
if DocumentRegistry:isImageFile(filepath) then
local RenderImage = require("ui/renderimage")
local ImageViewer = require("ui/widget/imageviewer")
UIManager:show(ImageViewer:new{
image = RenderImage:renderImageData(content, #content),
local index = 0
local curr_index
local images_list = {}
for i, item in ipairs(self.menu.item_table) do
local item_path = item.path
if item.is_file and DocumentRegistry:isImageFile(item_path) then
table.insert(images_list, item_path)
if not curr_index then
index = index + 1
if item_path == filepath then
curr_index = index
end
end
end
end
local image_table = { image_disposable = true }
setmetatable(image_table, {__index = function (_, key)
local content = self:extractContent(images_list[key])
if content then
return RenderImage:renderImageData(content, #content)
end
end
})
local viewer = ImageViewer:new{
image = image_table,
images_list_nb = #images_list,
fullscreen = true,
with_title_bar = false,
})
image_disposable = false,
}
UIManager:show(viewer)
viewer:switchToImageNum(curr_index)
else
local TextViewer = require("ui/widget/textviewer")
UIManager:show(TextViewer:new{
title = filepath,
title_multilines = true,
text = content,
text = self:extractContent(filepath),
justified = false,
})
end
end
function ArchiveViewer:extractFile(arcfile, filepath)
function ArchiveViewer:extractFile(filepath)
if self.arc_type == "zip" then
local std_out = io.popen("unzip ".."-qo \""..arcfile.."\"".." ".."\""..filepath.."\""..
" -d ".."\""..util.splitFilePathName(arcfile).."\"")
local std_out = io.popen(T(ZIP_EXTRACT_FILE, self.arc_file, filepath, util.splitFilePathName(self.arc_file)))
if std_out then
std_out:close()
end
@ -251,4 +286,18 @@ function ArchiveViewer:extractFile(arcfile, filepath)
self.fm_updated = true
end
function ArchiveViewer:extractContent(filepath)
local content
if self.arc_type == "zip" then
local std_out = io.popen(T(ZIP_EXTRACT_CONTENT, self.arc_file, filepath))
if std_out then
content = std_out:read("*all")
std_out:close()
return content
end
else
return
end
end
return ArchiveViewer

@ -8,6 +8,7 @@ local BD = require("ui/bidi")
local ConfirmBox = require("ui/widget/confirmbox")
local DataStorage = require("datastorage")
local Dispatcher = require("dispatcher")
local DocumentRegistry = require("document/documentregistry")
local Font = require("ui/font")
local QRMessage = require("ui/widget/qrmessage")
local InfoMessage = require("ui/widget/infomessage")
@ -28,6 +29,7 @@ local T = ffiutil.template
local TextEditor = WidgetContainer:extend{
name = "texteditor",
fullname = _("Text editor"),
settings_file = DataStorage:getSettingsDir() .. "/text_editor.lua",
settings = nil, -- loaded only when needed
-- how many to display in menu (10x3 pages minus our 3 default menu items):
@ -46,6 +48,25 @@ end
function TextEditor:init()
self:onDispatcherRegisterActions()
self.ui.menu:registerToMainMenu(self)
self:registerDocumentRegistryAuxProvider()
end
function TextEditor:registerDocumentRegistryAuxProvider()
DocumentRegistry:addAuxProvider({
provider_name = self.fullname,
provider = self.name,
order = 30, -- order in OpenWith dialog
disable_file = true,
disable_type = false,
})
end
function TextEditor:isFileTypeSupported(file)
return true
end
function TextEditor:openFile(file)
self:checkEditFile(file)
end
function TextEditor:loadSettings()
@ -94,7 +115,7 @@ end
function TextEditor:addToMainMenu(menu_items)
menu_items.text_editor = {
text = _("Text editor"),
text = self.fullname,
sub_item_table_func = function()
return self:getSubMenuItems()
end,

Loading…
Cancel
Save