local GUI = require("GUI") local screen = require("Screen") local filesystem = require("Filesystem") local event = require("Event") local system = require("System") local paths = require("Paths") local network = require("Network") local text = require("Text") local args, options = system.parseArguments(...) -------------------------------------------------------------------------------- local userSettings = system.getUserSettings() local localization = system.getSystemLocalization() local FTPMountPath = paths.system.mounts .. "FTP/" local configPath = paths.user.applicationData .. "Finder/Config.cfg" local config = { favourites = { { name = "Root", path = "/" }, { name = "Desktop", path = paths.user.desktop }, { name = "Applications", path = paths.system.applications }, { name = "Wallpapers", path = paths.system.wallpapers }, { name = "Libraries", path = paths.system.libraries }, { name = "User", path = paths.user.home }, { name = "Trash", path = paths.user.trash }, }, sidebarWidth = 20, gridMode = false, } if filesystem.exists(configPath) then config = filesystem.readTable(configPath) end local sidebarTitleColor = 0xC3C3C3 local sidebarItemColor = 0x5A5A5A local pathHistory = {} local pathHistoryCurrent = 0 -------------------------------------------------------------------------------- local windowsContainer = system.getWindowsContainer() local workspace, window, menu = system.addWindow(GUI.filledWindow( 1, 1, math.min(100, windowsContainer.width), math.min(26, windowsContainer.height), 0xF0F0F0 )) local titlePanel = window:addChild(GUI.panel(1, 1, 1, 3, 0x2D2D2D)) local prevButton = window:addChild(GUI.adaptiveButton(9, 2, 1, 0, 0x3C3C3C, 0xC3C3C3, 0xE1E1E1, 0x3C3C3C, "<")) prevButton.colors.disabled.background = 0x4B4B4B prevButton.colors.disabled.text = 0xA5A5A5 local nextButton = window:addChild(GUI.adaptiveButton(prevButton.localX + prevButton.width + 1, 2, 1, 0, 0x3C3C3C, 0xC3C3C3, 0xE1E1E1, 0x3C3C3C, ">")) nextButton.colors.disabled = prevButton.colors.disabled local modeList = window:addChild(GUI.list(nextButton.localX + nextButton.width + 2, 2, 10, 1, 2, 0, 0x3C3C3C, 0xE1E1E1, 0x4B4B4B, 0xE1E1E1, 0xE1E1E1, 0x4B4B4B, true)) modeList:setDirection(GUI.DIRECTION_HORIZONTAL) local FTPButton = window:addChild(GUI.adaptiveButton(modeList.localX + modeList.width + 2, 2, 1, 0, 0x3C3C3C, 0xC3C3C3, 0xE1E1E1, 0x3C3C3C, "FTP")) FTPButton.colors.disabled = prevButton.colors.disabled FTPButton.disabled = not network.internetProxy local sidebarContainer = window:addChild(GUI.container(1, 4, config.sidebarWidth, 1)) local sidebarPanel = system.addBlurredOrDefaultPanel(sidebarContainer, 1, 1, 1, 1) local sidebarItemsLayout = sidebarContainer:addChild(GUI.layout(1, 1, 1, 1, 1, 1)) sidebarItemsLayout:setAlignment(1, 1, GUI.ALIGNMENT_HORIZONTAL_LEFT, GUI.ALIGNMENT_VERTICAL_TOP) sidebarItemsLayout:setSpacing(1, 1, 0) sidebarItemsLayout:setMargin(1, 1, 0, 0) local searchInput = window:addChild(GUI.input(1, 2, 16, 1, 0x3C3C3C, 0xC3C3C3, 0x878787, 0x4B4B4B, 0xE1E1E1, nil, localization.search, true)) local iconField local statusContainer = window:addChild(GUI.container(FTPButton.localX + FTPButton.width + 2, 2, 1, 1)) local statusPanel = statusContainer:addChild(GUI.panel(1, 1, 1, 1, 0x3C3C3C)) local gotoButton = window:addChild(GUI.button(1, 2, 3, 1, 0x4B4B4B, 0xC3C3C3, 0xE1E1E1, 0x3C3C3C, "→")) local resizer = window:addChild(GUI.resizer(1, 1, 3, 4, 0xC3C3C3, 0x0)) window.actionButtons:moveToFront() -------------------------------------------------------------------------------- local function saveConfig() filesystem.writeTable(configPath, config) end local function getVerticalScroll() return config.gridMode and iconField.yOffset or iconField:getVerticalScroll() end local function setVerticalScroll(value) if config.gridMode then local iconsCount = #iconField.children - 1 local rows = math.ceil((iconsCount - 1) / iconField.iconCount.horizontal) local minimumOffset = (rows - 1) * (userSettings.iconHeight + userSettings.iconVerticalSpace) - userSettings.iconVerticalSpace value = math.max(-minimumOffset + 1, math.min(iconField.initialYOffset, value)) local delta = iconField.yOffset - value iconField.yOffset = value if iconsCount < 2 then return end local child for i = 2, iconsCount do child = iconField.children[i] if child ~= iconField.backgroundObject then child.localY = child.localY - delta end end else iconField:setVerticalScroll(value) end end local function updateFileListAndDraw() iconField:updateFileList() setVerticalScroll(config.gridMode and iconField.initialYOffset or 0) workspace:draw() end local function pathHistoryButtonsUpdate() prevButton.disabled = pathHistoryCurrent <= 1 nextButton.disabled = pathHistoryCurrent >= #pathHistory end local function prevOrNextpath(next) if next then if pathHistoryCurrent < #pathHistory then pathHistoryCurrent = pathHistoryCurrent + 1 end else if pathHistoryCurrent > 1 then pathHistoryCurrent = pathHistoryCurrent - 1 end end pathHistoryButtonsUpdate() iconField:setPath(pathHistory[pathHistoryCurrent]) updateFileListAndDraw() end local function addPath(path) pathHistoryCurrent = pathHistoryCurrent + 1 table.insert(pathHistory, pathHistoryCurrent, path) for i = pathHistoryCurrent + 1, #pathHistory do pathHistory[i] = nil end pathHistoryButtonsUpdate() searchInput.text = "" iconField:setPath(path) end local function sidebarItemDraw(object) local textColor, limit = object.textColor, object.width - 2 if object.path == iconField.path then textColor = 0xB4B4B4 screen.drawRectangle(object.x, object.y, object.width, 1, 0x2D2D2D, textColor, " ") screen.drawText(object.x, object.y, 0xFFDB80, "▎ ") if object.onRemove then limit = limit - 2 screen.drawText(object.x + object.width - 2, object.y, 0x3C3C3C, "x") end end screen.drawText(object.x + 1, object.y, textColor, text.limit(object.text, limit, "center")) end local function sidebarItemEventHandler(workspace, object, e1, e2, e3, ...) if e1 == "touch" then if object.onRemove and math.ceil(e3) == object.x + object.width - 2 then object.onRemove() elseif object.onTouch then object.onTouch(e1, e2, e3, ...) end end end local function addSidebarObject(textColor, text, path) local object = sidebarItemsLayout:addChild(GUI.object(1, 1, sidebarItemsLayout.width, 1)) object.textColor = textColor object.text = text object.path = path object.draw = sidebarItemDraw object.eventHandler = sidebarItemEventHandler return object end local function addSidebarTitle(...) return addSidebarObject(sidebarTitleColor, ...) end local function addSidebarItem(...) return addSidebarObject(sidebarItemColor, ...) end local function addSidebarSeparator() return sidebarItemsLayout:addChild(GUI.object(1, 1, sidebarItemsLayout.width, 1)) end local function onFavouriteTouch(path) addPath(path) updateFileListAndDraw() end local openFTP, updateSidebar openFTP = function(...) local mountPath = FTPMountPath .. network.getFTPProxyName(...) .. "/" addPath(mountPath) workspace:draw() local proxy, reason = network.connectToFTP(...) if proxy then network.unmountFTPs() filesystem.mount(proxy, mountPath) updateSidebar() updateFileListAndDraw() else GUI.alert(reason) end end updateSidebar = function() sidebarItemsLayout:removeChildren() -- Favourites addSidebarTitle(localization.favourite) for i = 1, #config.favourites do local object = addSidebarItem(" " .. filesystem.name(config.favourites[i].name), config.favourites[i].path) object.onTouch = function(e1, e2, e3) onFavouriteTouch(config.favourites[i].path) end object.onRemove = function() table.remove(config.favourites, i) updateSidebar() workspace:draw() saveConfig() end end addSidebarSeparator() -- Modem connections local added = false for proxy, path in filesystem.mounts() do if proxy.networkModem then if not added then addSidebarTitle(localization.network) added = true end addSidebarItem(" " .. network.getModemProxyName(proxy), path).onTouch = function() addPath(path) updateFileListAndDraw() end end end if added then addSidebarSeparator() end -- FTP connections if network.internetProxy and #userSettings.networkFTPConnections > 0 then addSidebarTitle(localization.networkFTPConnections) for i = 1, #userSettings.networkFTPConnections do local connection = userSettings.networkFTPConnections[i] local name = network.getFTPProxyName(connection.address, connection.port, connection.user) local object = addSidebarItem(" " .. name, FTPMountPath .. name .. "/") object.onTouch = function(e1, e2, e3, e4, e5) openFTP(connection.address, connection.port, connection.user, connection.password) end object.onRemove = function() table.remove(userSettings.networkFTPConnections, i) updateSidebar() workspace:draw() system.saveUserSettings() end end addSidebarSeparator() end -- Mounts addSidebarTitle(localization.mounts) for proxy, path in filesystem.mounts() do if not proxy.networkModem and not proxy.networkFTP then if proxy ~= filesystem.getProxy() then addSidebarItem(" " .. (proxy.getLabel() or proxy.address), path).onTouch = function() onFavouriteTouch(path) end end end end end sidebarItemsLayout.eventHandler = function(workspace, object, e1, e2, e3, e4, e5) if e1 == "scroll" then local cell = sidebarItemsLayout.cells[1][1] local from = 0 local to = -cell.childrenHeight + 1 cell.verticalMargin = cell.verticalMargin + (e5 > 0 and 1 or -1) if cell.verticalMargin > from then cell.verticalMargin = from elseif cell.verticalMargin < to then cell.verticalMargin = to end workspace:draw() elseif e1 == "component_added" or e1 == "component_removed" then FTPButton.disabled = not network.internetProxy updateSidebar() workspace:draw() elseif e1 == "network" then if e2 == "updateProxyList" or e2 == "timeout" then updateSidebar() workspace:draw() end end end searchInput.onInputFinished = function() iconField.filenameMatcher = searchInput.text updateFileListAndDraw() end nextButton.onTouch = function() prevOrNextpath(true) end prevButton.onTouch = function() prevOrNextpath(false) end FTPButton.onTouch = function() local container = GUI.addBackgroundContainer(workspace, true, true, localization.networkFTPNewConnection) local ad, po, us, pa if #userSettings.networkFTPConnections > 0 then local la = userSettings.networkFTPConnections[#userSettings.networkFTPConnections] ad, po, us, pa = la.address, tostring(la.port), la.user, la.password end local addressInput = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x696969, 0x969696, 0xE1E1E1, 0x2D2D2D, ad, localization.networkFTPAddress, true)) local portInput = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x696969, 0x969696, 0xE1E1E1, 0x2D2D2D, po, localization.networkFTPPort, true)) local userInput = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x696969, 0x969696, 0xE1E1E1, 0x2D2D2D, us, localization.networkFTPUser, true)) local passwordInput = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x696969, 0x969696, 0xE1E1E1, 0x2D2D2D, pa, localization.networkFTPPassword, true, "*")) container.layout:addChild(GUI.button(1, 1, 36, 3, 0x5A5A5A, 0xE1E1E1, 0x2D2D2D, 0xE1E1E1, "OK")).onTouch = function() container:remove() local port = tonumber(portInput.text) if port then local found = false for i = 1, #userSettings.networkFTPConnections do if userSettings.networkFTPConnections[i].address == addressInput.text and userSettings.networkFTPConnections[i].port == port and userSettings.networkFTPConnections[i].user == userInput.text and userSettings.networkFTPConnections[i].password == passwordInput.text then found = true break end end if not found then table.insert(userSettings.networkFTPConnections, { address = addressInput.text, port = port, user = userInput.text, password = passwordInput.text }) system.saveUserSettings() updateSidebar() workspace:draw() openFTP(addressInput.text, port, userInput.text, passwordInput.text) end end end workspace:draw() end local function calculateSizes() sidebarContainer.height = window.height - 3 sidebarPanel.width = sidebarContainer.width sidebarPanel.height = sidebarContainer.height sidebarItemsLayout.width = sidebarContainer.width sidebarItemsLayout.height = sidebarContainer.height for i = 1, #sidebarItemsLayout.children do sidebarItemsLayout.children[i].width = sidebarItemsLayout.width end resizer.localX = sidebarContainer.width resizer.localY = math.floor(4 + sidebarContainer.height / 2 - resizer.height / 2) window.backgroundPanel.width = window.width - sidebarContainer.width window.backgroundPanel.height = window.height - 3 window.backgroundPanel.localX = sidebarContainer.width + 1 window.backgroundPanel.localY = 4 titlePanel.width = window.width searchInput.localX = window.width - searchInput.width - 1 statusContainer.width = window.width - statusContainer.localX - searchInput.width - 6 statusPanel.width = statusContainer.width gotoButton.localX = statusContainer.localX + statusContainer.width iconField.width = window.backgroundPanel.width iconField.height = window.height + 3 iconField.localX = window.backgroundPanel.localX end local function updateIconField() local path if iconField then path = iconField.path iconField:remove() else path = paths.user.desktop end iconField = window:addChild( config.gridMode and system.gridIconField( 1, 4, 1, 1, 2, 2, path, 0x3C3C3C, 0xC3C3C3, 0x3C3C3C, 0x696969, nil ) or system.listIconField( 1, 4, 1, 1, path, 0xF0F0F0, 0xFFFFFF, 0x000000, nil, 0x3C3C3C, 0xE1E1E1, 0x3C3C3C, 0xCC2440, 0xFFFFFF ) ) iconField.blockScreenEvents = true iconField.launchers.directory = function(icon) addPath(icon.path) updateFileListAndDraw() end iconField.launchers.showPackageContent = function(icon) addPath(icon.path) updateFileListAndDraw() end iconField.launchers.showContainingFolder = function(icon) addPath(filesystem.path(system.readShortcut(icon.path))) updateFileListAndDraw() end iconField.eventHandler = function(workspace, self, e1, e2, e3, e4, e5, ...) if e1 == "scroll" then setVerticalScroll(getVerticalScroll() + (config.gridMode and e5 * 2 or e5)) workspace:draw() elseif e1 == "system" or e1 == "Finder" then if e2 == "updateFileList" then updateFileListAndDraw() elseif e2 == "updateFavourites" then if e3 then table.insert(config.favourites, e3) end saveConfig() updateSidebar() workspace:draw() end else if not config.gridMode then GUI.tableEventHandler(workspace, self, e1, e2, e3, e4, e5, ...) end end end local overrideUpdateFileList = iconField.updateFileList iconField.updateFileList = function(...) statusContainer:removeChildren(2) local x, path = 2, "/" local function addNode(text, path) statusContainer:addChild(GUI.adaptiveButton(x, 1, 0, 0, nil, 0xC3C3C3, nil, 0xFFFFFF, text)).onTouch = function() addPath(path) updateFileListAndDraw() end x = x + unicode.len(text) end addNode("root", "/") for node in iconField.path:gsub("/$", ""):gmatch("[^/]+") do statusContainer:addChild(GUI.text(x, 1, 0x696969, " ► ")) x = x + 3 path = path .. node .. "/" addNode(node, path) end if x > statusContainer.width then for i = 2, #statusContainer.children do statusContainer.children[i].localX = statusContainer.children[i].localX - (x - statusContainer.width) end end workspace:draw() overrideUpdateFileList(...) end resizer:moveToFront() calculateSizes() end gotoButton.onTouch = function() local input = window:addChild(GUI.input(statusContainer.localX, statusContainer.localY, statusContainer.width, 1, 0x4B4B4B, 0xC3C3C3, 0xC3C3C3, 0x4B4B4B, 0xC3C3C3, nil, nil)) input.onInputFinished = function() input:remove() statusContainer.hidden = false input.text = ("/" .. input.text .. "/"):gsub("/+", "/") if filesystem.exists(input.text) and filesystem.isDirectory(input.text) then addPath(input.text) iconField:updateFileList() end workspace:draw() end statusContainer.hidden = true input:startInput() end local overrideMaximize = window.actionButtons.maximize.onTouch window.actionButtons.maximize.onTouch = function() overrideMaximize() end window.actionButtons.close.onTouch = function() window:remove() end window.onResize = function(width, height) window.width = width window.height = height calculateSizes() workspace:draw() end window.onResizeFinished = function() updateFileListAndDraw() end resizer.onResize = function(deltaX) sidebarContainer.width = sidebarContainer.width + deltaX calculateSizes() workspace:draw() end resizer.onResizeFinished = function() updateFileListAndDraw() config.sidebarWidth = sidebarContainer.width saveConfig() end local function saveMode(gridMode) config.gridMode = gridMode updateIconField() updateFileListAndDraw() saveConfig() end modeList:addItem("☷").onTouch = function() saveMode(true) end modeList:addItem("☰").onTouch = function() saveMode(false) end -------------------------------------------------------------------------------- modeList.selectedItem = config.gridMode == nil and 2 or (config.gridMode and 1 or 2) updateIconField() if (options.o or options.open) and args[1] and filesystem.isDirectory(args[1]) then addPath(args[1]) else addPath("/") end updateSidebar() window:resize(window.width, window.height)