2019-12-06 21:55:39 +00:00
local BD = require ( " ui/bidi " )
2016-01-03 08:47:01 +00:00
local Blitbuffer = require ( " ffi/blitbuffer " )
2023-03-31 16:35:27 +00:00
local ButtonDialog = require ( " ui/widget/buttondialog " )
2021-12-30 11:52:04 +00:00
local CheckButton = require ( " ui/widget/checkbutton " )
2018-01-02 15:21:00 +00:00
local ConfirmBox = require ( " ui/widget/confirmbox " )
2017-04-12 18:42:28 +00:00
local Device = require ( " device " )
2020-07-12 18:47:49 +00:00
local DeviceListener = require ( " device/devicelistener " )
2016-06-04 05:05:14 +00:00
local DocSettings = require ( " docsettings " )
2017-04-12 18:42:28 +00:00
local DocumentRegistry = require ( " document/documentregistry " )
2013-10-18 20:38:07 +00:00
local Event = require ( " ui/event " )
2017-04-12 18:42:28 +00:00
local FileChooser = require ( " ui/widget/filechooser " )
2023-10-31 05:30:39 +00:00
local FileManagerBookInfo = require ( " apps/filemanager/filemanagerbookinfo " )
2019-11-05 23:17:28 +00:00
local FileManagerCollection = require ( " apps/filemanager/filemanagercollection " )
2017-04-12 18:42:28 +00:00
local FileManagerConverter = require ( " apps/filemanager/filemanagerconverter " )
2019-03-03 11:43:09 +00:00
local FileManagerFileSearcher = require ( " apps/filemanager/filemanagerfilesearcher " )
2017-04-12 18:42:28 +00:00
local FileManagerHistory = require ( " apps/filemanager/filemanagerhistory " )
local FileManagerMenu = require ( " apps/filemanager/filemanagermenu " )
2018-08-22 20:34:20 +00:00
local FileManagerShortcuts = require ( " apps/filemanager/filemanagershortcuts " )
2017-04-12 18:42:28 +00:00
local FrameContainer = require ( " ui/widget/container/framecontainer " )
2016-10-30 22:24:50 +00:00
local InfoMessage = require ( " ui/widget/infomessage " )
2017-04-12 18:42:28 +00:00
local InputContainer = require ( " ui/widget/container/inputcontainer " )
local InputDialog = require ( " ui/widget/inputdialog " )
2021-10-23 10:13:09 +00:00
local LanguageSupport = require ( " languagesupport " )
2023-05-19 07:55:49 +00:00
local Menu = require ( " ui/widget/menu " )
2018-07-28 18:30:39 +00:00
local MultiConfirmBox = require ( " ui/widget/multiconfirmbox " )
2024-05-20 06:37:40 +00:00
local NetworkListener = require ( " ui/network/networklistener " )
2016-12-20 07:19:54 +00:00
local PluginLoader = require ( " pluginloader " )
2019-11-05 23:17:28 +00:00
local ReadCollection = require ( " readcollection " )
2018-08-11 20:47:33 +00:00
local ReaderDeviceStatus = require ( " apps/reader/modules/readerdevicestatus " )
2017-01-02 18:12:34 +00:00
local ReaderDictionary = require ( " apps/reader/modules/readerdictionary " )
2017-05-12 16:28:42 +00:00
local ReaderWikipedia = require ( " apps/reader/modules/readerwikipedia " )
2023-10-13 04:54:35 +00:00
local ReadHistory = require ( " readhistory " )
2017-04-12 18:42:28 +00:00
local Screenshoter = require ( " ui/widget/screenshoter " )
2022-01-12 17:41:52 +00:00
local TitleBar = require ( " ui/widget/titlebar " )
2017-04-12 18:42:28 +00:00
local VerticalGroup = require ( " ui/widget/verticalgroup " )
local UIManager = require ( " ui/uimanager " )
2017-07-01 10:11:44 +00:00
local filemanagerutil = require ( " apps/filemanager/filemanagerutil " )
2017-04-12 18:42:28 +00:00
local lfs = require ( " libs/libkoreader-lfs " )
local logger = require ( " logger " )
2020-02-03 19:08:18 +00:00
local BaseUtil = require ( " ffi/util " )
local util = require ( " util " )
2017-04-12 18:42:28 +00:00
local _ = require ( " gettext " )
2019-08-24 07:25:38 +00:00
local C_ = _.pgettext
2021-12-16 11:12:25 +00:00
local N_ = _.ngettext
2017-04-12 18:42:28 +00:00
local Screen = Device.screen
2020-09-15 18:39:32 +00:00
local T = BaseUtil.template
2016-02-16 06:34:28 +00:00
2013-10-18 20:38:07 +00:00
local FileManager = InputContainer : extend {
2019-10-21 12:24:29 +00:00
title = _ ( " KOReader " ) ,
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
active_widgets = nil , -- array
2014-03-13 13:52:43 +00:00
root_path = lfs.currentdir ( ) ,
2021-12-16 12:46:53 +00:00
2021-12-16 11:12:25 +00:00
clipboard = nil , -- for single file operations
selected_files = nil , -- for group file operations
2015-04-22 06:15:04 +00:00
mv_bin = Device : isAndroid ( ) and " /system/bin/mv " or " /bin/mv " ,
cp_bin = Device : isAndroid ( ) and " /system/bin/cp " or " /bin/cp " ,
2013-08-14 09:29:05 +00:00
}
2023-12-10 06:05:34 +00:00
local function isFile ( file )
return lfs.attributes ( file , " mode " ) == " file "
end
2020-07-01 20:17:41 +00:00
function FileManager : onSetRotationMode ( rotation )
if rotation ~= nil and rotation ~= Screen : getRotationMode ( ) then
Screen : setRotationMode ( rotation )
2021-05-10 22:49:35 +00:00
if FileManager.instance then
self : reinit ( self.path , self.focused_file )
2020-07-01 20:17:41 +00:00
end
end
return true
end
2022-10-31 22:39:11 +00:00
function FileManager : onPhysicalKeyboardConnected ( )
-- So that the key navigation shortcuts apply right away.
-- This will also naturally call registerKeyEvents
self : reinit ( self.path , self.focused_file )
end
FileManager.onPhysicalKeyboardDisconnected = FileManager.onPhysicalKeyboardConnected
2021-01-07 22:58:30 +00:00
function FileManager : setRotationMode ( )
2020-07-09 17:11:44 +00:00
local locked = G_reader_settings : isTrue ( " lock_rotation " )
2021-01-07 22:58:30 +00:00
if not locked then
2022-12-21 14:50:39 +00:00
local rotation_mode = G_reader_settings : readSetting ( " fm_rotation_mode " ) or Screen.DEVICE_ROTATED_UPRIGHT
2020-07-01 20:17:41 +00:00
self : onSetRotationMode ( rotation_mode )
end
end
2020-12-19 15:27:53 +00:00
function FileManager : initGesListener ( )
if not Device : isTouchDevice ( ) then
return
end
self : registerTouchZones ( {
{
id = " filemanager_swipe " ,
ges = " swipe " ,
screen_zone = {
ratio_x = 0 , ratio_y = 0 ,
ratio_w = Screen : getWidth ( ) , ratio_h = Screen : getHeight ( ) ,
2019-08-04 17:59:20 +00:00
} ,
2020-12-19 15:27:53 +00:00
handler = function ( ges )
self : onSwipeFM ( ges )
end ,
} ,
} )
end
function FileManager : onSetDimensions ( dimen )
-- update listening according to new screen dimen
if Device : isTouchDevice ( ) then
2021-03-06 18:27:23 +00:00
self : updateTouchZonesOnScreenResize ( dimen )
2019-08-04 17:59:20 +00:00
end
2020-12-19 15:27:53 +00:00
end
2023-11-16 05:48:10 +00:00
function FileManager : updateTitleBarPath ( path )
path = path or self.file_chooser . path
local text = BD.directory ( filemanagerutil.abbreviate ( path ) )
if FileManagerShortcuts : hasFolderShortcut ( path ) then
text = " ☆ " .. text
end
self.title_bar : setSubTitle ( text )
end
2020-12-19 15:27:53 +00:00
function FileManager : setupLayout ( )
2014-03-13 13:52:43 +00:00
self.show_parent = self.show_parent or self
2022-01-12 17:41:52 +00:00
self.title_bar = TitleBar : new {
2024-06-06 08:44:03 +00:00
show_parent = self.show_parent ,
2022-01-12 17:41:52 +00:00
fullscreen = " true " ,
align = " center " ,
title = self.title ,
title_top_padding = Screen : scaleBySize ( 6 ) ,
2023-11-16 05:48:10 +00:00
subtitle = " " ,
2022-01-12 17:41:52 +00:00
subtitle_truncate_left = true ,
subtitle_fullwidth = true ,
button_padding = Screen : scaleBySize ( 5 ) ,
left_icon = " home " ,
left_icon_size_ratio = 1 ,
2023-04-05 05:24:41 +00:00
left_icon_tap_callback = function ( ) self : goHome ( ) end ,
left_icon_hold_callback = function ( ) self : onShowFolderMenu ( ) end ,
2024-06-06 08:44:03 +00:00
right_icon = self.selected_files and " check " or " plus " ,
2022-01-12 17:41:52 +00:00
right_icon_size_ratio = 1 ,
right_icon_tap_callback = function ( ) self : onShowPlusMenu ( ) end ,
right_icon_hold_callback = false , -- propagate long-press to dispatcher
2016-07-05 06:14:53 +00:00
}
2023-11-16 05:48:10 +00:00
self : updateTitleBarPath ( self.root_path )
2016-07-05 06:14:53 +00:00
2014-03-13 13:52:43 +00:00
local file_chooser = FileChooser : new {
2018-12-05 11:56:54 +00:00
-- remember to adjust the height when new item is added to the group
2014-03-13 13:52:43 +00:00
path = self.root_path ,
2017-10-21 17:53:56 +00:00
focused_path = self.focused_file ,
2014-03-13 13:52:43 +00:00
show_parent = self.show_parent ,
2022-01-12 17:41:52 +00:00
height = Screen : getHeight ( ) - self.title_bar : getHeight ( ) ,
2014-03-13 13:52:43 +00:00
is_popout = false ,
is_borderless = true ,
2023-10-12 05:58:52 +00:00
file_filter = function ( filename ) return DocumentRegistry : hasProvider ( filename ) end ,
2014-06-10 07:57:10 +00:00
close_callback = function ( ) return self : onClose ( ) end ,
2018-09-29 21:15:57 +00:00
-- allow left bottom tap gesture, otherwise it is eaten by hidden return button
return_arrow_propagation = true ,
2018-12-05 11:56:54 +00:00
-- allow Menu widget to delegate handling of some gestures to GestureManager
2021-12-16 11:12:25 +00:00
filemanager = self ,
2022-05-06 08:44:25 +00:00
-- let Menu widget merge our title_bar into its own TitleBar's FocusManager layout
outer_title_bar = self.title_bar ,
2014-03-13 13:52:43 +00:00
}
self.file_chooser = file_chooser
2017-10-21 17:53:56 +00:00
self.focused_file = nil -- use it only once
2014-03-13 13:52:43 +00:00
2021-12-16 11:12:25 +00:00
local file_manager = self
2024-06-06 08:44:03 +00:00
function file_chooser : onPathChanged ( path )
2023-11-16 05:48:10 +00:00
file_manager : updateTitleBarPath ( path )
2016-07-05 00:38:04 +00:00
return true
end
2024-06-06 08:44:03 +00:00
function file_chooser : onFileSelect ( item )
if file_manager.selected_files then -- toggle selection
item.dim = not item.dim and true or nil
file_manager.selected_files [ item.path ] = item.dim
2024-02-19 06:03:12 +00:00
self : updateItems ( )
2021-12-16 11:12:25 +00:00
else
2024-06-06 08:44:03 +00:00
file_manager : openFile ( item.path )
2021-12-16 11:12:25 +00:00
end
2014-03-13 13:52:43 +00:00
return true
end
2014-06-04 09:22:45 +00:00
2024-02-17 00:17:59 +00:00
function file_chooser : onFileHold ( item )
2024-06-06 08:44:03 +00:00
if file_manager.selected_files then
2022-10-04 11:44:17 +00:00
file_manager : tapPlus ( )
else
2024-02-17 00:17:59 +00:00
self : showFileDialog ( item )
2022-10-04 11:44:17 +00:00
end
end
2024-06-06 08:44:03 +00:00
function file_chooser : showFileDialog ( item )
2024-02-17 00:17:59 +00:00
local file = item.path
local is_file = item.is_file
local is_not_parent_folder = not item.is_go_up
2023-11-16 05:48:10 +00:00
local function close_dialog_callback ( )
UIManager : close ( self.file_dialog )
end
local function refresh_callback ( )
self : refreshPath ( )
end
local function close_dialog_refresh_callback ( )
UIManager : close ( self.file_dialog )
self : refreshPath ( )
end
2015-03-12 08:29:15 +00:00
local buttons = {
{
2014-03-13 13:52:43 +00:00
{
2019-08-24 07:25:38 +00:00
text = C_ ( " File " , " Copy " ) ,
2021-03-31 20:37:08 +00:00
enabled = is_not_parent_folder ,
2015-03-12 08:29:15 +00:00
callback = function ( )
UIManager : close ( self.file_dialog )
2023-02-17 21:06:55 +00:00
file_manager : copyFile ( file )
2015-03-12 08:29:15 +00:00
end ,
2014-03-13 13:52:43 +00:00
} ,
{
2019-08-24 07:25:38 +00:00
text = C_ ( " File " , " Paste " ) ,
2021-12-16 11:12:25 +00:00
enabled = file_manager.clipboard and true or false ,
2015-03-12 08:29:15 +00:00
callback = function ( )
UIManager : close ( self.file_dialog )
2023-12-10 06:05:34 +00:00
file_manager : pasteFileFromClipboard ( file )
2015-03-12 08:29:15 +00:00
end ,
2014-03-13 13:52:43 +00:00
} ,
2016-12-03 13:27:32 +00:00
{
2022-10-04 11:44:17 +00:00
text = _ ( " Select " ) ,
2016-12-03 13:27:32 +00:00
callback = function ( )
2022-10-04 11:44:17 +00:00
UIManager : close ( self.file_dialog )
2024-06-06 08:44:03 +00:00
file_manager : onToggleSelectMode ( )
2022-10-04 11:44:17 +00:00
if is_file then
file_manager.selected_files [ file ] = true
2024-02-19 06:03:12 +00:00
item.dim = true
self : updateItems ( )
2022-10-04 11:44:17 +00:00
end
2016-12-03 13:27:32 +00:00
end ,
} ,
2014-03-13 13:52:43 +00:00
} ,
2015-03-12 08:29:15 +00:00
{
{
text = _ ( " Cut " ) ,
2021-03-31 20:37:08 +00:00
enabled = is_not_parent_folder ,
2015-03-12 08:29:15 +00:00
callback = function ( )
UIManager : close ( self.file_dialog )
2023-02-17 21:06:55 +00:00
file_manager : cutFile ( file )
2015-03-12 08:29:15 +00:00
end ,
} ,
{
text = _ ( " Delete " ) ,
2021-03-31 20:37:08 +00:00
enabled = is_not_parent_folder ,
2015-03-12 08:29:15 +00:00
callback = function ( )
2021-04-27 19:22:16 +00:00
UIManager : close ( self.file_dialog )
2023-11-16 05:48:10 +00:00
file_manager : showDeleteFileDialog ( file , refresh_callback )
2015-03-12 08:29:15 +00:00
end ,
} ,
2016-02-18 17:03:27 +00:00
{
text = _ ( " Rename " ) ,
2021-03-31 20:37:08 +00:00
enabled = is_not_parent_folder ,
2016-02-18 17:03:27 +00:00
callback = function ( )
UIManager : close ( self.file_dialog )
2023-02-17 21:06:55 +00:00
file_manager : showRenameFileDialog ( file , is_file )
2016-02-18 17:03:27 +00:00
end ,
}
2015-03-12 08:29:15 +00:00
} ,
2023-02-17 21:06:55 +00:00
{ } , -- separator
2019-11-05 23:17:28 +00:00
}
2020-02-03 19:08:18 +00:00
2021-03-31 20:37:08 +00:00
if is_file then
2024-02-07 08:35:52 +00:00
self.book_props = nil -- in 'self' to provide access to it in CoverBrowser
2023-10-12 05:58:52 +00:00
local has_provider = DocumentRegistry : hasProvider ( file )
2024-02-07 08:35:52 +00:00
local has_sidecar = DocSettings : hasSidecarFile ( file )
if has_provider or has_sidecar then
self.book_props = file_manager.coverbrowser and file_manager.coverbrowser : getBookInfo ( file )
local doc_settings_or_file
if has_sidecar then
doc_settings_or_file = DocSettings : open ( file )
if not self.book_props then
local props = doc_settings_or_file : readSetting ( " doc_props " )
self.book_props = FileManagerBookInfo.extendProps ( props , file )
self.book_props . has_cover = true -- to enable "Book cover" button, we do not know if cover exists
end
else
doc_settings_or_file = file
end
table.insert ( buttons , filemanagerutil.genStatusButtonsRow ( doc_settings_or_file , close_dialog_refresh_callback ) )
2023-02-17 21:06:55 +00:00
table.insert ( buttons , { } ) -- separator
2023-10-12 05:58:52 +00:00
table.insert ( buttons , {
2024-02-07 08:35:52 +00:00
filemanagerutil.genResetSettingsButton ( doc_settings_or_file , close_dialog_refresh_callback ) ,
2024-05-26 05:18:44 +00:00
file_manager.collections : genAddToCollectionButton ( file , close_dialog_callback , refresh_callback ) ,
2023-10-12 05:58:52 +00:00
} )
2023-02-04 20:32:43 +00:00
end
2019-11-05 23:17:28 +00:00
table.insert ( buttons , {
2018-02-02 20:21:52 +00:00
{
text = _ ( " Open with… " ) ,
callback = function ( )
UIManager : close ( self.file_dialog )
2023-11-05 05:24:18 +00:00
file_manager : showOpenWithDialog ( file )
2018-02-02 20:21:52 +00:00
end ,
} ,
2024-02-07 08:35:52 +00:00
filemanagerutil.genBookInformationButton ( file , self.book_props , close_dialog_callback ) ,
2023-03-07 20:24:42 +00:00
} )
2023-10-12 05:58:52 +00:00
if has_provider then
table.insert ( buttons , {
2024-02-07 08:35:52 +00:00
filemanagerutil.genBookCoverButton ( file , self.book_props , close_dialog_callback ) ,
filemanagerutil.genBookDescriptionButton ( file , self.book_props , close_dialog_callback ) ,
2023-10-12 05:58:52 +00:00
} )
end
2023-02-17 21:06:55 +00:00
if Device : canExecuteScript ( file ) then
table.insert ( buttons , {
2023-03-31 16:35:27 +00:00
filemanagerutil.genExecuteScriptButton ( file , close_dialog_callback ) ,
2023-02-17 21:06:55 +00:00
} )
end
2019-11-05 23:17:28 +00:00
if FileManagerConverter : isSupported ( file ) then
table.insert ( buttons , {
{
text = _ ( " Convert " ) ,
callback = function ( )
UIManager : close ( self.file_dialog )
FileManagerConverter : showConvertButtons ( file , self )
end ,
2023-03-31 16:35:27 +00:00
} ,
2019-11-05 23:17:28 +00:00
} )
end
2024-02-17 00:17:59 +00:00
else -- folder
2023-11-16 05:48:10 +00:00
local folder = BaseUtil.realpath ( file )
2015-03-12 08:29:15 +00:00
table.insert ( buttons , {
{
2021-02-22 17:44:16 +00:00
text = _ ( " Set as HOME folder " ) ,
2015-03-12 08:29:15 +00:00
callback = function ( )
UIManager : close ( self.file_dialog )
2023-11-16 05:48:10 +00:00
file_manager : setHome ( folder )
2015-03-12 08:29:15 +00:00
end
2023-03-31 16:35:27 +00:00
} ,
2015-03-12 08:29:15 +00:00
} )
2023-11-16 05:48:10 +00:00
table.insert ( buttons , {
file_manager.folder_shortcuts : genAddRemoveShortcutButton ( folder , close_dialog_callback , refresh_callback )
} )
2015-03-12 08:29:15 +00:00
end
2016-12-11 00:06:10 +00:00
2023-06-08 05:27:52 +00:00
self.file_dialog = ButtonDialog : new {
2023-02-17 21:06:55 +00:00
title = is_file and BD.filename ( file : match ( " ([^/]+)$ " ) ) or BD.directory ( file : match ( " ([^/]+)$ " ) ) ,
2016-12-11 00:06:10 +00:00
title_align = " center " ,
2015-03-12 08:29:15 +00:00
buttons = buttons ,
2014-03-13 13:52:43 +00:00
}
UIManager : show ( self.file_dialog )
return true
end
self.layout = VerticalGroup : new {
2022-01-12 17:41:52 +00:00
self.title_bar ,
2014-03-13 13:52:43 +00:00
file_chooser ,
}
local fm_ui = FrameContainer : new {
padding = 0 ,
bordersize = 0 ,
2014-10-22 13:34:11 +00:00
background = Blitbuffer.COLOR_WHITE ,
2014-03-13 13:52:43 +00:00
self.layout ,
}
self [ 1 ] = fm_ui
self.menu = FileManagerMenu : new {
ui = self
}
2020-11-08 21:43:37 +00:00
2022-10-31 22:39:11 +00:00
self : registerKeyEvents ( )
end
function FileManager : registerKeyEvents ( )
2022-11-01 22:22:07 +00:00
-- NOTE: We need to be surgical here, because this is called through reinit at runtime.
2020-12-19 15:27:53 +00:00
if Device : hasKeys ( ) then
2022-10-27 00:01:51 +00:00
self.key_events . Home = { { " Home " } }
2020-12-19 15:27:53 +00:00
-- Override the menu.lua way of handling the back key
2022-10-27 00:01:51 +00:00
self.file_chooser . key_events.Back = { { Device.input . group.Back } }
2024-05-26 19:25:54 +00:00
if Device : hasScreenKB ( ) then
2024-06-06 08:44:03 +00:00
self.key_events . ToggleWifi = { { " ScreenKB " , " Home " } }
2024-05-20 19:20:50 +00:00
end
2020-12-19 15:27:53 +00:00
if not Device : hasFewKeys ( ) then
-- Also remove the handler assigned to the "Back" key by menu.lua
self.file_chooser . key_events.Close = nil
end
2022-10-31 22:39:11 +00:00
else
self.key_events . Home = nil
self.file_chooser . key_events.Back = nil
self.file_chooser . key_events.Close = nil
2020-12-19 15:27:53 +00:00
end
end
2021-10-25 06:41:20 +00:00
function FileManager : registerModule ( name , ui_module , always_active )
if name then
self [ name ] = ui_module
ui_module.name = " filemanager " .. name
end
table.insert ( self , ui_module )
if always_active then
-- to get events even when hidden
table.insert ( self.active_widgets , ui_module )
end
end
ReaderUI: Saner FM/RD lifecycle
* Ensure that going from one to the other tears down the former and
its plugins before instantiating the latter and its plugins.
UIManager: Unify Event sending & broadcasting
* Make the two behave the same way (walk the widget stack from top to
bottom), and properly handle the window stack shrinking shrinking
*and* growing.
Previously, broadcasting happened bottom-to-top and didn't really
handle the list shrinking/growing, while sending only handled the list
shrinking by a single element, and hopefully that element being the one
the event was just sent to.
These two items combined allowed us to optimize suboptimal
refresh behavior with Menu and other Menu classes when
opening/closing a document.
e.g., the "opening document" Notification is now properly regional,
and the "open last doc" option no longer flashes like a crazy person
anymore.
Plugins: Allow optimizing Menu refresh with custom menus, too.
Requires moving Menu's close_callback *after* onMenuSelect, which, eh,
probably makes sense, and is probably harmless in the grand scheme of
things.
2021-05-01 16:53:04 +00:00
-- NOTE: The only thing that will *ever* instantiate a new FileManager object is our very own showFiles below!
2020-12-19 15:27:53 +00:00
function FileManager : init ( )
self : setupLayout ( )
2021-10-25 06:41:20 +00:00
self.active_widgets = { }
self : registerModule ( " screenshot " , Screenshoter : new {
2023-11-05 05:24:18 +00:00
prefix = " FileManager " ,
2021-10-25 06:41:20 +00:00
ui = self ,
} , true )
self : registerModule ( " menu " , self.menu )
self : registerModule ( " history " , FileManagerHistory : new { ui = self } )
2023-10-31 05:30:39 +00:00
self : registerModule ( " bookinfo " , FileManagerBookInfo : new { ui = self } )
2021-10-25 06:41:20 +00:00
self : registerModule ( " collections " , FileManagerCollection : new { ui = self } )
self : registerModule ( " filesearcher " , FileManagerFileSearcher : new { ui = self } )
self : registerModule ( " folder_shortcuts " , FileManagerShortcuts : new { ui = self } )
self : registerModule ( " languagesupport " , LanguageSupport : new { ui = self } )
self : registerModule ( " dictionary " , ReaderDictionary : new { ui = self } )
self : registerModule ( " wikipedia " , ReaderWikipedia : new { ui = self } )
self : registerModule ( " devicestatus " , ReaderDeviceStatus : new { ui = self } )
self : registerModule ( " devicelistener " , DeviceListener : new { ui = self } )
2024-05-20 06:37:40 +00:00
self : registerModule ( " networklistener " , NetworkListener : new { ui = self } )
2014-03-13 13:52:43 +00:00
2016-12-20 07:19:54 +00:00
-- koreader plugins
2020-12-19 15:27:53 +00:00
for _ , plugin_module in ipairs ( PluginLoader : loadPlugins ( ) ) do
if not plugin_module.is_doc_only then
local ok , plugin_or_err = PluginLoader : createPluginInstance (
plugin_module , { ui = self , } )
-- Keep references to the modules which do not register into menu.
if ok then
2021-10-25 06:41:20 +00:00
self : registerModule ( plugin_module.name , plugin_or_err )
logger.dbg ( " FM loaded plugin " , plugin_module.name ,
2020-12-19 15:27:53 +00:00
" at " , plugin_module.path )
2017-04-21 06:37:04 +00:00
end
2016-12-20 07:19:54 +00:00
end
end
2018-09-29 21:15:57 +00:00
2021-03-06 18:27:23 +00:00
self : initGesListener ( )
2014-03-13 13:52:43 +00:00
self : handleEvent ( Event : new ( " SetDimensions " , self.dimen ) )
2021-05-13 11:05:05 +00:00
if FileManager.instance == nil then
logger.dbg ( " Spinning up new FileManager instance " , tostring ( self ) )
else
2021-05-18 18:54:54 +00:00
-- Should never happen, given what we did in showFiles...
2021-05-13 11:05:05 +00:00
logger.err ( " FileManager instance mismatch! Opened " , tostring ( self ) , " while we still have an existing instance: " , tostring ( FileManager.instance ) , debug.traceback ( ) )
end
FileManager.instance = self
2013-08-14 09:29:05 +00:00
end
2018-04-09 07:22:16 +00:00
function FileChooser : onBack ( )
2021-11-21 17:47:00 +00:00
local back_to_exit = G_reader_settings : readSetting ( " back_to_exit " , " prompt " )
local back_in_filemanager = G_reader_settings : readSetting ( " back_in_filemanager " , " default " )
2018-10-10 16:28:51 +00:00
if back_in_filemanager == " default " then
if back_to_exit == " always " then
return self : onClose ( )
elseif back_to_exit == " disable " then
return true
elseif back_to_exit == " prompt " then
UIManager : show ( ConfirmBox : new {
text = _ ( " Exit KOReader? " ) ,
ok_text = _ ( " Exit " ) ,
ok_callback = function ( )
self : onClose ( )
end
} )
return true
end
elseif back_in_filemanager == " parent_folder " then
self : changeToPath ( string.format ( " %s/.. " , self.path ) )
2018-04-09 07:22:16 +00:00
return true
end
end
2019-08-04 17:59:20 +00:00
function FileManager : onSwipeFM ( ges )
2019-12-06 21:55:39 +00:00
local direction = BD.flipDirectionIfMirroredUILayout ( ges.direction )
if direction == " west " then
2019-08-04 17:59:20 +00:00
self.file_chooser : onNextPage ( )
2019-12-06 21:55:39 +00:00
elseif direction == " east " then
2019-08-04 17:59:20 +00:00
self.file_chooser : onPrevPage ( )
end
return true
end
2021-12-16 11:12:25 +00:00
function FileManager : onShowPlusMenu ( )
self : tapPlus ( )
return true
end
2024-06-06 08:44:03 +00:00
function FileManager : onToggleSelectMode ( do_refresh )
2021-12-16 11:12:25 +00:00
logger.dbg ( " toggle select mode " )
2024-06-06 08:44:03 +00:00
if self.selected_files then
self.selected_files = nil
self.title_bar : setRightIcon ( " plus " )
if do_refresh then
self.file_chooser : refreshPath ( )
else
self.file_chooser : selectAllFilesInFolder ( false ) -- undim
end
else
self.selected_files = { }
self.title_bar : setRightIcon ( " check " )
2022-10-04 11:44:17 +00:00
end
2024-06-06 08:44:03 +00:00
return true
2021-12-16 11:12:25 +00:00
end
2018-01-01 15:31:39 +00:00
function FileManager : tapPlus ( )
2023-11-16 05:48:10 +00:00
local function close_dialog_callback ( )
UIManager : close ( self.file_dialog )
end
local function refresh_titlebar_callback ( )
self : updateTitleBarPath ( )
end
2021-12-16 11:12:25 +00:00
local title , buttons
2024-06-06 08:44:03 +00:00
if self.selected_files then
2024-05-01 06:01:59 +00:00
local function toggle_select_mode_callback ( )
2024-06-06 08:44:03 +00:00
self : onToggleSelectMode ( true )
2024-05-01 06:01:59 +00:00
end
2021-12-16 11:12:25 +00:00
local select_count = util.tableSize ( self.selected_files )
local actions_enabled = select_count > 0
title = actions_enabled and T ( N_ ( " 1 file selected " , " %1 files selected " , select_count ) , select_count )
or _ ( " No files selected " )
buttons = {
2024-05-01 06:01:59 +00:00
{
self.collections : genAddToCollectionButton ( self.selected_files , close_dialog_callback , toggle_select_mode_callback ) ,
} ,
2018-01-01 15:31:39 +00:00
{
2021-12-16 11:12:25 +00:00
{
2023-05-19 07:55:49 +00:00
text = _ ( " Show selected files list " ) ,
enabled = actions_enabled ,
2021-12-16 11:12:25 +00:00
callback = function ( )
2023-02-17 21:06:55 +00:00
UIManager : close ( self.file_dialog )
2023-05-19 07:55:49 +00:00
self : showSelectedFilesList ( )
2021-12-16 11:12:25 +00:00
end ,
} ,
{
text = _ ( " Copy " ) ,
enabled = actions_enabled ,
callback = function ( )
2023-12-10 06:05:34 +00:00
self.cutfile = false
self : showCopyMoveSelectedFilesDialog ( close_dialog_callback )
end ,
2021-12-16 11:12:25 +00:00
} ,
2018-01-01 15:31:39 +00:00
} ,
{
2021-12-16 11:12:25 +00:00
{
2023-05-19 07:55:49 +00:00
text = _ ( " Select all files in folder " ) ,
2021-12-16 11:12:25 +00:00
callback = function ( )
2023-02-17 21:06:55 +00:00
UIManager : close ( self.file_dialog )
2024-02-19 06:03:12 +00:00
self.file_chooser : selectAllFilesInFolder ( true )
2021-12-16 11:12:25 +00:00
end ,
} ,
{
text = _ ( " Move " ) ,
enabled = actions_enabled ,
callback = function ( )
2023-12-10 06:05:34 +00:00
self.cutfile = true
self : showCopyMoveSelectedFilesDialog ( close_dialog_callback )
end ,
2021-12-16 11:12:25 +00:00
} ,
2018-01-01 15:31:39 +00:00
} ,
{
2021-12-16 11:12:25 +00:00
{
2023-05-19 07:55:49 +00:00
text = _ ( " Deselect all " ) ,
enabled = actions_enabled ,
2021-12-16 11:12:25 +00:00
callback = function ( )
UIManager : close ( self.file_dialog )
2023-12-10 06:05:34 +00:00
for file in pairs ( self.selected_files ) do
self.selected_files [ file ] = nil
end
2024-02-19 06:03:12 +00:00
self.file_chooser : selectAllFilesInFolder ( false ) -- undim
2021-12-16 11:12:25 +00:00
end ,
} ,
{
text = _ ( " Delete " ) ,
enabled = actions_enabled ,
callback = function ( )
UIManager : show ( ConfirmBox : new {
text = _ ( " Delete selected files? \n If you delete a file, it is permanently lost. " ) ,
ok_text = _ ( " Delete " ) ,
ok_callback = function ( )
2023-02-17 21:06:55 +00:00
UIManager : close ( self.file_dialog )
2023-12-10 06:05:34 +00:00
self : deleteSelectedFiles ( )
2021-12-16 11:12:25 +00:00
end ,
} )
2023-05-19 07:55:49 +00:00
end ,
} ,
} ,
{
{
text = _ ( " Exit select mode " ) ,
callback = function ( )
UIManager : close ( self.file_dialog )
self : onToggleSelectMode ( )
end ,
} ,
{
text = _ ( " Export highlights " ) ,
enabled = ( actions_enabled and self.exporter ) and true or false ,
callback = function ( )
self.exporter : exportFilesNotes ( self.selected_files )
end ,
2021-12-16 11:12:25 +00:00
} ,
} ,
2023-02-17 21:06:55 +00:00
{ } , -- separator
2021-12-30 11:52:04 +00:00
{
{
text = _ ( " New folder " ) ,
callback = function ( )
UIManager : close ( self.file_dialog )
self : createFolder ( )
end ,
} ,
2023-11-16 05:48:10 +00:00
self.folder_shortcuts : genShowFolderShortcutsButton ( close_dialog_callback ) ,
2021-12-30 11:52:04 +00:00
} ,
2021-12-16 11:12:25 +00:00
}
else
title = BD.dirpath ( filemanagerutil.abbreviate ( self.file_chooser . path ) )
buttons = {
2018-07-28 18:30:39 +00:00
{
2021-12-16 11:12:25 +00:00
{
text = _ ( " Select files " ) ,
callback = function ( )
UIManager : close ( self.file_dialog )
2024-06-06 08:44:03 +00:00
self : onToggleSelectMode ( )
2021-12-16 11:12:25 +00:00
end ,
} ,
} ,
2018-08-22 20:34:20 +00:00
{
2021-12-16 11:12:25 +00:00
{
text = _ ( " New folder " ) ,
callback = function ( )
UIManager : close ( self.file_dialog )
2021-12-30 11:52:04 +00:00
self : createFolder ( )
2021-12-16 11:12:25 +00:00
end ,
} ,
} ,
2019-12-21 14:59:23 +00:00
{
2021-12-16 11:12:25 +00:00
{
text = _ ( " Paste " ) ,
enabled = self.clipboard and true or false ,
callback = function ( )
UIManager : close ( self.file_dialog )
2023-12-10 06:05:34 +00:00
self : pasteFileFromClipboard ( )
2021-12-16 11:12:25 +00:00
end ,
} ,
2019-12-21 14:59:23 +00:00
} ,
2020-11-27 19:51:40 +00:00
{
2021-12-16 11:12:25 +00:00
{
text = _ ( " Set as HOME folder " ) ,
callback = function ( )
UIManager : close ( self.file_dialog )
2023-12-10 06:05:34 +00:00
self : setHome ( )
2020-11-27 19:51:40 +00:00
end
2023-11-16 05:48:10 +00:00
} ,
2021-12-16 11:12:25 +00:00
} ,
{
{
text = _ ( " Go to HOME folder " ) ,
callback = function ( )
2020-11-27 19:51:40 +00:00
UIManager : close ( self.file_dialog )
2023-02-17 21:06:55 +00:00
self : goHome ( )
2021-12-16 11:12:25 +00:00
end
2023-11-16 05:48:10 +00:00
} ,
2021-12-16 11:12:25 +00:00
} ,
{
{
text = _ ( " Open random document " ) ,
callback = function ( )
2020-11-27 19:51:40 +00:00
UIManager : close ( self.file_dialog )
2023-02-17 21:06:55 +00:00
self : openRandomFile ( self.file_chooser . path )
2020-11-27 19:51:40 +00:00
end
2023-11-16 05:48:10 +00:00
} ,
2020-11-27 19:51:40 +00:00
} ,
2021-12-16 11:12:25 +00:00
{
2023-11-16 05:48:10 +00:00
self.folder_shortcuts : genShowFolderShortcutsButton ( close_dialog_callback ) ,
} ,
{
self.folder_shortcuts : genAddRemoveShortcutButton ( self.file_chooser . path , close_dialog_callback , refresh_titlebar_callback ) ,
} ,
2021-12-16 11:12:25 +00:00
}
if Device : hasExternalSD ( ) then
2023-02-17 21:06:55 +00:00
table.insert ( buttons , 4 , { -- after "Paste" or "Import files here" button
2021-12-16 11:12:25 +00:00
{
text_func = function ( )
2023-02-17 21:06:55 +00:00
return Device : isValidPath ( self.file_chooser . path )
and _ ( " Switch to SDCard " ) or _ ( " Switch to internal storage " )
2021-12-16 11:12:25 +00:00
end ,
callback = function ( )
2023-02-17 21:06:55 +00:00
UIManager : close ( self.file_dialog )
2021-12-16 11:12:25 +00:00
if Device : isValidPath ( self.file_chooser . path ) then
local ok , sd_path = Device : hasExternalSD ( )
if ok then
self.file_chooser : changeToPath ( sd_path )
end
else
self.file_chooser : changeToPath ( Device.home_dir )
end
end ,
} ,
} )
end
2023-02-17 21:06:55 +00:00
if Device : canImportFiles ( ) then
table.insert ( buttons , 4 , { -- always after "Paste" button
{
text = _ ( " Import files here " ) ,
enabled = Device : isValidPath ( self.file_chooser . path ) ,
callback = function ( )
UIManager : close ( self.file_dialog )
Device.importFile ( self.file_chooser . path )
end ,
} ,
} )
end
2020-11-27 19:51:40 +00:00
end
2023-06-08 05:27:52 +00:00
self.file_dialog = ButtonDialog : new {
2021-12-16 11:12:25 +00:00
title = title ,
2018-01-01 15:31:39 +00:00
title_align = " center " ,
buttons = buttons ,
2024-06-06 08:44:03 +00:00
select_mode = self.selected_files and true or nil , -- for coverbrowser
2018-01-01 15:31:39 +00:00
}
UIManager : show ( self.file_dialog )
end
2017-10-13 16:43:02 +00:00
function FileManager : reinit ( path , focused_file )
2020-08-20 03:31:57 +00:00
UIManager : flushSettings ( )
2016-04-23 14:56:56 +00:00
self.dimen = Screen : getSize ( )
2015-02-01 17:36:49 +00:00
-- backup the root path and path items
2024-05-16 18:42:08 +00:00
self.root_path = BaseUtil.realpath ( path or self.file_chooser . path )
2015-02-01 17:36:49 +00:00
local path_items_backup = { }
for k , v in pairs ( self.file_chooser . path_items ) do
path_items_backup [ k ] = v
end
-- reinit filemanager
2017-10-21 17:53:56 +00:00
self.focused_file = focused_file
2020-12-19 15:27:53 +00:00
self : setupLayout ( )
self : handleEvent ( Event : new ( " SetDimensions " , self.dimen ) )
2015-02-01 17:36:49 +00:00
self.file_chooser . path_items = path_items_backup
2020-12-19 15:27:53 +00:00
-- self:init() has already done file_chooser:refreshPath()
-- (by virtue of rebuilding file_chooser), so this one
-- looks unnecessary (cheap with classic mode, less cheap with
2017-10-21 17:53:56 +00:00
-- CoverBrowser plugin's cover image renderings)
-- self:onRefresh()
2015-02-01 17:36:49 +00:00
end
2020-06-19 10:22:38 +00:00
function FileManager : getCurrentDir ( )
2023-02-17 21:06:55 +00:00
return FileManager.instance and FileManager.instance . file_chooser.path
2020-06-19 10:22:38 +00:00
end
2013-08-14 09:29:05 +00:00
function FileManager : onClose ( )
2016-12-29 08:10:38 +00:00
logger.dbg ( " close filemanager " )
2021-04-29 17:30:18 +00:00
PluginLoader : finalize ( )
2020-02-17 15:53:09 +00:00
self : handleEvent ( Event : new ( " SaveSettings " ) )
2017-09-04 19:05:05 +00:00
G_reader_settings : flush ( )
2014-03-13 13:52:43 +00:00
UIManager : close ( self )
return true
2013-08-14 09:29:05 +00:00
end
2013-10-18 20:38:07 +00:00
2021-05-10 22:49:35 +00:00
function FileManager : onCloseWidget ( )
if FileManager.instance == self then
logger.dbg ( " Tearing down FileManager " , tostring ( self ) )
else
logger.warn ( " FileManager instance mismatch! Closed " , tostring ( self ) , " while the active one is supposed to be " , tostring ( FileManager.instance ) )
end
FileManager.instance = nil
end
ReaderUI: Saner FM/RD lifecycle
* Ensure that going from one to the other tears down the former and
its plugins before instantiating the latter and its plugins.
UIManager: Unify Event sending & broadcasting
* Make the two behave the same way (walk the widget stack from top to
bottom), and properly handle the window stack shrinking shrinking
*and* growing.
Previously, broadcasting happened bottom-to-top and didn't really
handle the list shrinking/growing, while sending only handled the list
shrinking by a single element, and hopefully that element being the one
the event was just sent to.
These two items combined allowed us to optimize suboptimal
refresh behavior with Menu and other Menu classes when
opening/closing a document.
e.g., the "opening document" Notification is now properly regional,
and the "open last doc" option no longer flashes like a crazy person
anymore.
Plugins: Allow optimizing Menu refresh with custom menus, too.
Requires moving Menu's close_callback *after* onMenuSelect, which, eh,
probably makes sense, and is probably harmless in the grand scheme of
things.
2021-05-01 16:53:04 +00:00
function FileManager : onShowingReader ( )
-- Allows us to optimize out a few useless refreshes in various CloseWidgets handlers...
self.tearing_down = true
-- Clear the dither flag to prevent it from infecting the queue and re-inserting a full-screen refresh...
self.dithered = nil
self : onClose ( )
end
-- Same as above, except we don't close it yet. Useful for plugins that need to close custom Menus before calling showReader.
function FileManager : onSetupShowReader ( )
self.tearing_down = true
self.dithered = nil
end
2014-09-10 04:21:07 +00:00
function FileManager : onRefresh ( )
self.file_chooser : refreshPath ( )
return true
end
2017-09-27 16:15:11 +00:00
function FileManager : goHome ( )
2022-01-11 12:47:06 +00:00
if not self.file_chooser : goHome ( ) then
2020-04-10 17:11:41 +00:00
self : setHome ( )
2017-09-27 16:15:11 +00:00
end
return true
end
function FileManager : setHome ( path )
path = path or self.file_chooser . path
UIManager : show ( ConfirmBox : new {
2021-02-22 17:44:16 +00:00
text = T ( _ ( " Set '%1' as HOME folder? " ) , BD.dirpath ( path ) ) ,
2017-09-27 16:15:11 +00:00
ok_text = _ ( " Set as HOME " ) ,
ok_callback = function ( )
G_reader_settings : saveSetting ( " home_dir " , path )
2023-08-18 05:11:43 +00:00
if G_reader_settings : isTrue ( " lock_home_folder " ) then
self : onRefresh ( )
end
2017-09-27 16:15:11 +00:00
end ,
} )
return true
end
2018-07-28 18:30:39 +00:00
function FileManager : openRandomFile ( dir )
2023-11-05 05:24:18 +00:00
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 )
2018-07-28 18:30:39 +00:00
if random_file then
2023-03-31 16:35:27 +00:00
UIManager : show ( MultiConfirmBox : new {
2020-02-03 19:08:18 +00:00
text = T ( _ ( " Do you want to open %1? " ) , BD.filename ( BaseUtil.basename ( random_file ) ) ) ,
2018-07-28 18:30:39 +00:00
choice1_text = _ ( " Open " ) ,
choice1_callback = function ( )
2021-05-10 22:49:35 +00:00
local ReaderUI = require ( " apps/reader/readerui " )
2018-07-28 18:30:39 +00:00
ReaderUI : showReader ( random_file )
end ,
2019-08-24 07:25:38 +00:00
-- @translators Another file. This is a button on the open random file dialog. It presents a file with the choices Open/Another.
2018-07-28 18:30:39 +00:00
choice2_text = _ ( " Another " ) ,
choice2_callback = function ( )
self : openRandomFile ( dir )
end ,
} )
else
2023-03-31 16:35:27 +00:00
UIManager : show ( InfoMessage : new {
2018-07-28 18:30:39 +00:00
text = _ ( " File not found " ) ,
} )
end
end
2014-01-18 15:15:44 +00:00
function FileManager : copyFile ( file )
2014-03-13 13:52:43 +00:00
self.cutfile = false
self.clipboard = file
2014-01-18 15:15:44 +00:00
end
function FileManager : cutFile ( file )
2014-03-13 13:52:43 +00:00
self.cutfile = true
self.clipboard = file
2014-01-18 15:15:44 +00:00
end
2023-12-10 06:05:34 +00:00
function FileManager : pasteFileFromClipboard ( file )
2023-02-17 21:06:55 +00:00
local orig_file = BaseUtil.realpath ( self.clipboard )
2023-12-10 06:05:34 +00:00
local orig_name = BaseUtil.basename ( orig_file )
2023-08-18 05:11:43 +00:00
local dest_path = BaseUtil.realpath ( file or self.file_chooser . path )
2023-12-10 06:05:34 +00:00
dest_path = isFile ( dest_path ) and dest_path : match ( " (.*/) " ) or dest_path
2023-02-17 21:06:55 +00:00
local dest_file = BaseUtil.joinPath ( dest_path , orig_name )
2024-05-26 05:19:45 +00:00
if orig_file == dest_file or orig_file == dest_path then -- do not paste to itself
self.clipboard = nil
return
end
2023-12-10 06:05:34 +00:00
local is_file = isFile ( orig_file )
2023-02-17 21:06:55 +00:00
2023-12-10 06:05:34 +00:00
local function doPaste ( )
local ok
if self.cutfile then
ok = self : moveFile ( orig_file , dest_path )
else
ok = self : copyRecursive ( orig_file , dest_path )
end
if ok then
if is_file then -- move or copy sdr
DocSettings.updateLocation ( orig_file , dest_file , not self.cutfile )
2018-01-02 15:21:00 +00:00
end
2023-12-10 06:05:34 +00:00
if self.cutfile then -- for move only
if is_file then
ReadHistory : updateItem ( orig_file , dest_file )
ReadCollection : updateItem ( orig_file , dest_file )
else
ReadHistory : updateItemsByPath ( orig_file , dest_file )
ReadCollection : updateItemsByPath ( orig_file , dest_file )
end
end
self.clipboard = nil
self : onRefresh ( )
2023-02-17 21:06:55 +00:00
else
2023-12-10 06:05:34 +00:00
local text = self.cutfile and " Failed to move: \n %1 \n to: \n %2 "
or " Failed to copy: \n %1 \n to: \n %2 "
2023-03-31 16:35:27 +00:00
UIManager : show ( InfoMessage : new {
2023-12-10 06:05:34 +00:00
text = T ( _ ( text ) , BD.filepath ( orig_name ) , BD.dirpath ( dest_path ) ) ,
2023-02-17 21:06:55 +00:00
icon = " notice-warning " ,
} )
2018-01-02 15:21:00 +00:00
end
2023-02-17 21:06:55 +00:00
end
2018-01-02 15:21:00 +00:00
2023-12-10 06:05:34 +00:00
local mode_dest = lfs.attributes ( dest_file , " mode " )
if mode_dest then -- file or folder with target name already exists
local can_overwrite = ( mode_dest == " file " ) == is_file
local text = can_overwrite == is_file and T ( _ ( " File already exists: \n %1 " ) , BD.filename ( orig_name ) )
or T ( _ ( " Folder already exists: \n %1 " ) , BD.directory ( orig_name ) )
if can_overwrite then
UIManager : show ( ConfirmBox : new {
text = text ,
ok_text = _ ( " Overwrite " ) ,
ok_callback = function ( )
doPaste ( )
end ,
} )
2018-01-02 15:21:00 +00:00
else
2023-03-31 16:35:27 +00:00
UIManager : show ( InfoMessage : new {
2023-12-10 06:05:34 +00:00
text = text ,
2023-02-17 21:06:55 +00:00
icon = " notice-warning " ,
} )
2018-01-02 15:21:00 +00:00
end
2023-12-10 06:05:34 +00:00
else
doPaste ( )
2023-02-17 21:06:55 +00:00
end
2023-12-10 06:05:34 +00:00
end
2018-01-02 15:21:00 +00:00
2023-12-10 06:05:34 +00:00
function FileManager : showCopyMoveSelectedFilesDialog ( close_callback )
local text , ok_text
if self.cutfile then
text = _ ( " Move selected files to the current folder? " )
ok_text = _ ( " Move " )
else
text = _ ( " Copy selected files to the current folder? " )
ok_text = _ ( " Copy " )
end
local confirmbox , check_button_overwrite
confirmbox = ConfirmBox : new {
text = text ,
ok_text = ok_text ,
ok_callback = function ( )
close_callback ( )
self : pasteSelectedFiles ( check_button_overwrite.checked )
end ,
}
check_button_overwrite = CheckButton : new {
text = _ ( " overwrite existing files " ) ,
checked = true ,
parent = confirmbox ,
}
confirmbox : addWidget ( check_button_overwrite )
UIManager : show ( confirmbox )
end
function FileManager : pasteSelectedFiles ( overwrite )
local dest_path = BaseUtil.realpath ( self.file_chooser . path )
local ok_files = { }
for orig_file in pairs ( self.selected_files ) do
local orig_name = BaseUtil.basename ( orig_file )
local dest_file = BaseUtil.joinPath ( dest_path , orig_name )
2024-05-26 05:19:45 +00:00
if BaseUtil.realpath ( orig_file ) == dest_file then -- do not paste to itself
2023-12-10 06:05:34 +00:00
self.selected_files [ orig_file ] = nil
2024-05-26 05:19:45 +00:00
else
local ok
local dest_mode = lfs.attributes ( dest_file , " mode " )
if not dest_mode or ( dest_mode == " file " and overwrite ) then
if self.cutfile then
ok = self : moveFile ( orig_file , dest_path )
else
ok = self : copyRecursive ( orig_file , dest_path )
end
end
if ok then
DocSettings.updateLocation ( orig_file , dest_file , not self.cutfile )
ok_files [ orig_file ] = true
self.selected_files [ orig_file ] = nil
end
2023-12-10 06:05:34 +00:00
end
end
local skipped_nb = util.tableSize ( self.selected_files )
if util.tableSize ( ok_files ) > 0 then
if self.cutfile then -- for move only
ReadHistory : updateItems ( ok_files , dest_path )
ReadCollection : updateItems ( ok_files , dest_path )
end
if skipped_nb > 0 then
2018-01-02 15:21:00 +00:00
self : onRefresh ( )
end
2018-01-01 15:31:39 +00:00
end
2023-12-10 06:05:34 +00:00
if skipped_nb > 0 then -- keep select mode on
local text = self.cutfile and T ( N_ ( " 1 file was not moved " , " %1 files were not moved " , skipped_nb ) , skipped_nb )
or T ( N_ ( " 1 file was not copied " , " %1 files were not copied " , skipped_nb ) , skipped_nb )
UIManager : show ( InfoMessage : new {
text = text ,
icon = " notice-warning " ,
2023-02-17 21:06:55 +00:00
} )
else
2024-06-06 08:44:03 +00:00
self : onToggleSelectMode ( true )
2023-02-17 21:06:55 +00:00
end
2018-01-01 15:31:39 +00:00
end
2021-12-30 11:52:04 +00:00
function FileManager : createFolder ( )
local input_dialog , check_button_enter_folder
input_dialog = InputDialog : new {
title = _ ( " New folder " ) ,
buttons = {
{
{
text = _ ( " Cancel " ) ,
2022-03-04 20:20:00 +00:00
id = " close " ,
2021-12-30 11:52:04 +00:00
callback = function ( )
UIManager : close ( input_dialog )
end ,
} ,
{
text = _ ( " Create " ) ,
is_enter_default = true ,
callback = function ( )
local new_folder_name = input_dialog : getInputText ( )
if new_folder_name == " " then return end
UIManager : close ( input_dialog )
local new_folder = string.format ( " %s/%s " , self.file_chooser . path , new_folder_name )
2023-02-17 21:06:55 +00:00
if util.makePath ( new_folder ) then
2021-12-30 11:52:04 +00:00
if check_button_enter_folder.checked then
self.file_chooser : changeToPath ( new_folder )
else
self.file_chooser : refreshPath ( )
end
else
UIManager : show ( InfoMessage : new {
text = T ( _ ( " Failed to create folder: \n %1 " ) , BD.directory ( new_folder_name ) ) ,
icon = " notice-warning " ,
} )
end
end ,
} ,
}
} ,
}
check_button_enter_folder = CheckButton : new {
text = _ ( " Enter folder after creation " ) ,
checked = false ,
parent = input_dialog ,
}
input_dialog : addWidget ( check_button_enter_folder )
UIManager : show ( input_dialog )
input_dialog : onShowKeyboard ( )
2018-01-05 20:24:24 +00:00
end
2023-12-10 06:05:34 +00:00
function FileManager : showDeleteFileDialog ( filepath , post_delete_callback , pre_delete_callback )
local file = BaseUtil.realpath ( filepath )
if file == nil then
UIManager : show ( InfoMessage : new {
text = T ( _ ( " File not found: \n %1 " ) , BD.filepath ( filepath ) ) ,
icon = " notice-warning " ,
} )
return
end
local is_file = isFile ( file )
2023-02-17 21:06:55 +00:00
local text = ( is_file and _ ( " Delete file permanently? " ) or _ ( " Delete folder permanently? " ) ) .. " \n \n " .. BD.filepath ( file )
2023-12-10 06:05:34 +00:00
if is_file and DocSettings : hasSidecarFile ( file ) then
2023-02-17 21:06:55 +00:00
text = text .. " \n \n " .. _ ( " Book settings, highlights and notes will be deleted. " )
end
UIManager : show ( ConfirmBox : new {
text = text ,
ok_text = _ ( " Delete " ) ,
ok_callback = function ( )
if pre_delete_callback then
pre_delete_callback ( )
end
if self : deleteFile ( file , is_file ) and post_delete_callback then
post_delete_callback ( )
end
end ,
} )
end
function FileManager : deleteFile ( file , is_file )
if is_file then
2023-12-10 06:05:34 +00:00
local ok = os.remove ( file )
if ok then
DocSettings.updateLocation ( file ) -- delete sdr
2023-10-13 04:54:35 +00:00
ReadHistory : fileDeleted ( file )
2023-12-10 06:05:34 +00:00
ReadCollection : removeItem ( file )
return true
2016-01-07 06:44:07 +00:00
end
2014-11-13 11:28:57 +00:00
else
2023-12-10 06:05:34 +00:00
local ok = BaseUtil.purgeDir ( file )
if ok then
ReadHistory : folderDeleted ( file ) -- will delete sdr
ReadCollection : removeItemsByPath ( file )
return true
end
end
UIManager : show ( InfoMessage : new {
text = T ( _ ( " Failed to delete: \n %1 " ) , BD.filepath ( file ) ) ,
icon = " notice-warning " ,
} )
end
function FileManager : deleteSelectedFiles ( )
local ok_files = { }
for orig_file in pairs ( self.selected_files ) do
local file_abs_path = BaseUtil.realpath ( orig_file )
local ok = file_abs_path and os.remove ( file_abs_path )
if ok then
DocSettings.updateLocation ( file_abs_path ) -- delete sdr
ok_files [ orig_file ] = true
self.selected_files [ orig_file ] = nil
end
end
local skipped_nb = util.tableSize ( self.selected_files )
if util.tableSize ( ok_files ) > 0 then
ReadHistory : removeItems ( ok_files )
ReadCollection : removeItems ( ok_files )
if skipped_nb > 0 then
self : onRefresh ( )
end
end
if skipped_nb > 0 then -- keep select mode on
2014-11-13 11:28:57 +00:00
UIManager : show ( InfoMessage : new {
2023-12-10 06:05:34 +00:00
text = T ( N_ ( " Failed to delete 1 file. " , " Failed to delete %1 files. " , skipped_nb ) , skipped_nb ) ,
2021-04-27 19:22:16 +00:00
icon = " notice-warning " ,
2014-11-13 11:28:57 +00:00
} )
2023-12-10 06:05:34 +00:00
else
2024-06-06 08:44:03 +00:00
self : onToggleSelectMode ( true )
2014-11-13 11:28:57 +00:00
end
2014-01-18 15:15:44 +00:00
end
2023-02-17 21:06:55 +00:00
function FileManager : showRenameFileDialog ( file , is_file )
local dialog
dialog = InputDialog : new {
title = is_file and _ ( " Rename file " ) or _ ( " Rename folder " ) ,
input = BaseUtil.basename ( file ) ,
buttons = { {
{
text = _ ( " Cancel " ) ,
id = " close " ,
callback = function ( )
UIManager : close ( dialog )
end ,
} ,
{
text = _ ( " Rename " ) ,
callback = function ( )
local new_name = dialog : getInputText ( )
if new_name ~= " " then
UIManager : close ( dialog )
self : renameFile ( file , new_name , is_file )
2021-06-29 12:01:44 +00:00
end
2023-02-17 21:06:55 +00:00
end ,
} ,
} } ,
}
UIManager : show ( dialog )
dialog : onShowKeyboard ( )
end
function FileManager : renameFile ( file , basename , is_file )
if BaseUtil.basename ( file ) == basename then return end
local dest = BaseUtil.joinPath ( BaseUtil.dirname ( file ) , basename )
local function doRenameFile ( )
if self : moveFile ( file , dest ) then
if is_file then
2023-10-31 05:30:39 +00:00
DocSettings.updateLocation ( file , dest )
2023-12-10 06:05:34 +00:00
ReadHistory : updateItem ( file , dest ) -- (will update "lastfile" if needed)
ReadCollection : updateItem ( file , dest )
2023-10-13 04:54:35 +00:00
else
ReadHistory : updateItemsByPath ( file , dest )
2023-12-10 06:05:34 +00:00
ReadCollection : updateItemsByPath ( file , dest )
2021-06-29 12:01:44 +00:00
end
2023-02-17 21:06:55 +00:00
self : onRefresh ( )
else
UIManager : show ( InfoMessage : new {
text = T ( _ ( " Failed to rename: \n %1 \n to: \n %2 " ) , BD.filepath ( file ) , BD.filepath ( dest ) ) ,
icon = " notice-warning " ,
} )
2021-06-29 12:01:44 +00:00
end
2023-02-17 21:06:55 +00:00
end
2021-06-29 12:01:44 +00:00
2023-02-17 21:06:55 +00:00
local mode_dest = lfs.attributes ( dest , " mode " )
if mode_dest then
local text , ok_text
if ( mode_dest == " file " ) ~= is_file then
if is_file then
text = T ( _ ( " Folder already exists: \n %1 \n File cannot be renamed. " ) , BD.directory ( basename ) )
2021-06-29 12:01:44 +00:00
else
2023-02-17 21:06:55 +00:00
text = T ( _ ( " File already exists: \n %1 \n Folder cannot be renamed. " ) , BD.filename ( basename ) )
2016-02-18 17:03:27 +00:00
end
2023-03-31 16:35:27 +00:00
UIManager : show ( InfoMessage : new {
2023-02-17 21:06:55 +00:00
text = text ,
icon = " notice-warning " ,
} )
2016-02-18 17:03:27 +00:00
else
2023-02-17 21:06:55 +00:00
if is_file then
text = T ( _ ( " File already exists: \n %1 \n Overwrite file? " ) , BD.filename ( basename ) )
ok_text = _ ( " Overwrite " )
else
text = T ( _ ( " Folder already exists: \n %1 \n Move the folder inside it? " ) , BD.directory ( basename ) )
ok_text = _ ( " Move " )
end
2023-03-31 16:35:27 +00:00
UIManager : show ( ConfirmBox : new {
2023-02-17 21:06:55 +00:00
text = text ,
ok_text = ok_text ,
ok_callback = function ( )
doRenameFile ( )
end ,
} )
2016-02-18 17:03:27 +00:00
end
2023-02-17 21:06:55 +00:00
else
doRenameFile ( )
2016-02-18 17:03:27 +00:00
end
end
2021-05-18 18:54:54 +00:00
--- @note: This is the *only* safe way to instantiate a new FileManager instance!
2024-06-06 08:44:03 +00:00
function FileManager : showFiles ( path , focused_file , selected_files )
2021-05-10 22:49:35 +00:00
-- Warn about and close any pre-existing FM instances first...
if FileManager.instance then
logger.warn ( " FileManager instance mismatch! Tried to spin up a new instance, while we still have an existing one: " , tostring ( FileManager.instance ) )
-- Close the old one first!
FileManager.instance : onClose ( )
end
2024-05-16 18:42:08 +00:00
path = BaseUtil.realpath ( path or G_reader_settings : readSetting ( " lastdir " ) or filemanagerutil.getDefaultDir ( ) )
2016-02-02 02:25:23 +00:00
G_reader_settings : saveSetting ( " lastdir " , path )
2020-07-01 20:17:41 +00:00
self : setRotationMode ( )
2016-02-02 02:25:23 +00:00
local file_manager = FileManager : new {
dimen = Screen : getSize ( ) ,
2018-03-17 22:02:32 +00:00
covers_fullscreen = true , -- hint for UIManager:_repaint()
2016-02-02 02:25:23 +00:00
root_path = path ,
2017-10-21 17:53:56 +00:00
focused_file = focused_file ,
2024-06-06 08:44:03 +00:00
selected_files = selected_files ,
2016-02-02 02:25:23 +00:00
}
UIManager : show ( file_manager )
end
2020-05-02 21:02:36 +00:00
--- A shortcut to execute mv.
-- @treturn boolean result of mv command
2016-02-18 17:03:27 +00:00
function FileManager : moveFile ( from , to )
2020-02-03 19:08:18 +00:00
return BaseUtil.execute ( self.mv_bin , from , to ) == 0
2016-02-18 17:03:27 +00:00
end
2020-05-02 21:02:36 +00:00
--- A shortcut to execute cp.
-- @treturn boolean result of cp command
function FileManager : copyFileFromTo ( from , to )
return BaseUtil.execute ( self.cp_bin , from , to ) == 0
end
--- A shortcut to execute cp recursively.
-- @treturn boolean result of cp command
function FileManager : copyRecursive ( from , to )
return BaseUtil.execute ( self.cp_bin , " -r " , from , to ) == 0
end
2018-03-14 21:16:38 +00:00
function FileManager : onHome ( )
return self : goHome ( )
end
2021-05-05 18:43:43 +00:00
function FileManager : onRefreshContent ( )
self : onRefresh ( )
end
2023-09-06 06:41:10 +00:00
function FileManager : onBookMetadataChanged ( )
self : onRefresh ( )
end
2023-03-31 16:35:27 +00:00
function FileManager : onShowFolderMenu ( )
local button_dialog
local function genButton ( button_text , button_path )
return { {
text = button_text ,
align = " left " ,
font_face = " smallinfofont " ,
font_size = 22 ,
font_bold = false ,
avoid_text_truncation = false ,
callback = function ( )
UIManager : close ( button_dialog )
self.file_chooser : changeToPath ( button_path )
end ,
hold_callback = function ( )
return true -- do not move the menu
end ,
} }
end
local home_dir = G_reader_settings : readSetting ( " home_dir " ) or filemanagerutil.getDefaultDir ( )
local home_dir_shortened = G_reader_settings : nilOrTrue ( " shorten_home_dir " )
local home_dir_not_locked = G_reader_settings : nilOrFalse ( " lock_home_folder " )
2023-04-02 13:46:51 +00:00
local home_dir_suffix = " \u{f015} " -- "home" character
2023-03-31 16:35:27 +00:00
local buttons = { }
-- root folder
local text
local path = " / "
local is_home = path == home_dir
local home_found = is_home or home_dir_not_locked
if home_found then
text = path
if is_home and home_dir_shortened then
text = text .. home_dir_suffix
end
table.insert ( buttons , genButton ( text , path ) )
end
-- other folders
local indent = " "
for part in self.file_chooser . path : gmatch ( " ([^/]+) " ) do
text = ( # buttons == 0 and path or indent .. " └ " ) .. part
path = path .. part .. " / "
is_home = path == home_dir or path == home_dir .. " / "
if not home_found and is_home then
home_found = true
end
if home_found then
if is_home and home_dir_shortened then
text = text .. home_dir_suffix
end
table.insert ( buttons , genButton ( text , path ) )
indent = indent .. " "
end
end
button_dialog = ButtonDialog : new {
shrink_unneeded_width = true ,
buttons = buttons ,
anchor = function ( )
return self.title_bar . left_button.image . dimen
end ,
}
UIManager : show ( button_dialog )
end
2023-05-19 07:55:49 +00:00
function FileManager : showSelectedFilesList ( )
local selected_files = { }
for file in pairs ( self.selected_files ) do
2023-08-10 15:01:07 +00:00
table.insert ( selected_files , {
2023-08-18 05:11:43 +00:00
text = filemanagerutil.abbreviate ( file ) ,
filepath = file ,
2023-08-10 15:01:07 +00:00
bidi_wrap_func = BD.filepath ,
} )
2023-05-19 07:55:49 +00:00
end
local function sorting ( a , b )
local a_path , a_name = util.splitFilePathName ( a.text )
local b_path , b_name = util.splitFilePathName ( b.text )
if a_path == b_path then
return BaseUtil.strcoll ( a_name , b_name )
end
return BaseUtil.strcoll ( a_path , b_path )
end
table.sort ( selected_files , sorting )
2024-02-19 06:03:12 +00:00
local menu
menu = Menu : new {
title = T ( _ ( " Selected files (%1) " ) , # selected_files ) ,
item_table = selected_files ,
2023-05-19 07:55:49 +00:00
is_borderless = true ,
is_popout = false ,
2024-06-06 08:44:03 +00:00
title_bar_fm_style = true ,
2023-08-10 15:01:07 +00:00
truncate_left = true ,
2023-05-19 07:55:49 +00:00
onMenuSelect = function ( _ , item )
2024-02-19 06:03:12 +00:00
UIManager : close ( menu )
2023-08-18 05:11:43 +00:00
self.file_chooser : changeToPath ( util.splitFilePathName ( item.filepath ) , item.filepath )
2023-05-19 07:55:49 +00:00
end ,
close_callback = function ( )
2024-02-19 06:03:12 +00:00
UIManager : close ( menu )
2023-05-19 07:55:49 +00:00
end ,
}
2024-02-19 06:03:12 +00:00
UIManager : show ( menu )
2023-05-19 07:55:49 +00:00
end
2023-11-05 05:24:18 +00:00
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
2024-06-26 07:05:45 +00:00
if associated_providers ~= nil and next ( associated_providers ) ~= nil then
2023-11-05 05:24:18 +00:00
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 = { }
2024-02-07 08:35:52 +00:00
for extension , provider_key in pairs ( associated_providers ) do
2023-11-05 05:24:18 +00:00
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
2024-02-07 08:35:52 +00:00
table.sort ( t )
2023-11-05 05:24:18 +00:00
UIManager : show ( InfoMessage : new {
text = table.concat ( t , " \n " ) ,
monospace_font = true ,
} )
end ,
} } )
end
-- row: 2 buttons
table.insert ( buttons , {
{
text = _ ( " Cancel " ) ,
2024-06-16 08:38:02 +00:00
id = " close " ,
2023-11-05 05:24:18 +00:00
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 )
2024-02-07 08:35:52 +00:00
if provider == nil then
provider = DocumentRegistry : getProvider ( file , true ) -- include auxiliary
2023-11-05 05:24:18 +00:00
end
2024-02-17 00:17:59 +00:00
if provider and provider.order then -- auxiliary
2023-11-05 05:24:18 +00:00
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
2024-06-01 07:33:14 +00:00
-- Dispatcher helpers
function FileManager . getDisplayModeActions ( )
local action_names , action_texts = { " classic " } , { _ ( " Classic (filename only) " ) }
local ui = FileManager.instance or require ( " apps/reader/readerui " ) . instance
if ui.coverbrowser then
for _ , v in ipairs ( ui.coverbrowser . modes ) do
local action_text , action_name = unpack ( v )
if action_name then -- skip Classic
table.insert ( action_names , action_name )
table.insert ( action_texts , action_text )
end
end
end
return action_names , action_texts
end
function FileManager : onSetDisplayMode ( mode )
if self.coverbrowser then
mode = mode ~= " classic " and mode or nil
self.coverbrowser : setDisplayMode ( mode )
end
return true
end
function FileManager . getSortByActions ( )
local collates = { }
for k , v in pairs ( FileChooser.collates ) do
table.insert ( collates , {
name = k ,
text = v.text ,
menu_order = v.menu_order ,
} )
end
table.sort ( collates , function ( a , b ) return a.menu_order < b.menu_order end )
local action_names , action_texts = { } , { }
for _ , v in ipairs ( collates ) do
table.insert ( action_names , v.name )
table.insert ( action_texts , v.text )
end
return action_names , action_texts
end
function FileManager : onSetSortBy ( mode )
G_reader_settings : saveSetting ( " collate " , mode )
self.file_chooser : clearSortingCache ( )
self.file_chooser : refreshPath ( )
return true
end
function FileManager : onSetReverseSorting ( toggle )
G_reader_settings : saveSetting ( " reverse_collate " , toggle or nil )
self.file_chooser : refreshPath ( )
return true
end
function FileManager : onSetMixedSorting ( toggle )
G_reader_settings : saveSetting ( " collate_mixed " , toggle or nil )
self.file_chooser : refreshPath ( )
return true
end
2013-10-18 20:38:07 +00:00
return FileManager