Advertisement
Virgilcore

RobcOS File Browser (Lattix)

Dec 15th, 2022
971
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 32.51 KB | None | 0 0
  1. --[[
  2.     Release version of Lattix, version a0.2
  3.     A lot of features from Lattice weren't adopted yet :/ but at least you can enjoy the extra stability
  4.    
  5.     Made by Konlab
  6.    
  7.     If you wonder why the screen is so stable (no flickering) then it's because of the awesomeness of CrazedProgrammmer's Surface API, go check it out here: http://www.computercraft.info/forums2/index.php?/topic/22397-surface-api-162/
  8. ]]
  9.  
  10. local tArgs = { "/R_Files", "/R_Files"}
  11. local version = "a0.2"
  12.  
  13. --CONFIG
  14. --colors of lattix
  15. local lat_theme = {
  16.     menu_back = colors.green;
  17.     menu_text = colors.black;
  18.     main_back = colors.black;
  19.     main_text = colors.green;
  20.     selected_text = colors.cyan;
  21.     dir_text = colors.yellow;
  22.     multiselect_back = colors.lightGray;
  23.     blank_back = colors.black;
  24.     blank_text = colors.green;
  25. }
  26. --colors of lattix on basic comps
  27. local lat_basic_theme = {
  28.     menu_back = colors.gray;
  29.     menu_text = colors.white;
  30.     main_back = colors.white;
  31.     main_text = colors.black;
  32.     selected_text = colors.lightGray;
  33.     dir_text = colors.black;
  34.     multiselect_back = colors.white;
  35.     blank_back = colors.white;
  36.     blank_text = colors.black;
  37. }
  38. local dirprefix = "Terminal/" --this text gets added before dirs
  39. local basic_dirprefix = "[] " --this text gets added before dirs on non-advanced computers
  40. local selectprefix = "" --this text gets added before selected things (even in multiselect)
  41. local basic_selectprefix = "> " --this text gets added before selected things (even in multiselect) on non-advanced computers
  42. --which programs to use to open .<xtension> files. Special codes: "run" and "api" and special extension "def" - def will be used for every extension not listed (def must be something), run runs it without args, api loads it with os.loadAPI (yes ikr very useful rn since os.loadAPI doesn't like weird extensions). The empty string indicates no extension
  43. --the file is opened by running the program chosen below with arg #1 being the path to the file
  44. local defaultPrograms = {
  45.     [""] = "/rom/programs/edit.lua",
  46.     ["txt"] = "/rom/programs/edit.lua",
  47.     ["cfg"] = "/rom/programs/edit.lua",
  48.     ["config"] = "/rom/programs/edit.lua",
  49.     ["nfp"] = "/rom/programs/fun/advanced/paint.lua",
  50.     ["lua"] = "run",
  51. --  ["api"] = "api", --if a custom OS sets up os.loadAPI to work with .api and stores its apis in .api then this could work
  52.     ["def"] = "/rom/programs/edit.lua"
  53. }
  54. --which programs can be used to create files (think clicking the New... button) Special code: choose means the program is chosen manually through a dialog
  55. --these programs are run with the path to the file that is to be created as first argument
  56. local editors = {
  57.     ["Other"] = "choose",
  58.     ["NFP painting"] = "/rom/programs/fun/advanced/paint.lua",
  59.     ["Lua script"] = "/rom/programs/edit.lua",
  60.     ["Text file"] = "/rom/programs/edit.lua"
  61.    
  62.    
  63.    
  64. }
  65. --Extensions for the above editors
  66. local editors_extensions = {
  67.     ["NFP painting"] = "nfp",
  68.     ["Lua script"] = "lua",
  69.     ["Text file"] = "txt"
  70.    
  71.    
  72. }
  73.  
  74. local surfacepath = "surface"
  75.  
  76. --code adopted and modified from CC's source code
  77. local function pastebinGet(paste, path)
  78.    
  79.     if not http then
  80.         error("HTTP disabled", 2)
  81.     end
  82.     --- Attempts to guess the pastebin ID from the given code or URL
  83.     local extractId = function (paste)
  84.         local patterns = {
  85.             "^([%a%d]+)$",
  86.             "^https?://pastebin.com/([%a%d]+)$",
  87.             "^pastebin.com/([%a%d]+)$",
  88.             "^https?://pastebin.com/raw/([%a%d]+)$",
  89.             "^pastebin.com/raw/([%a%d]+)$",
  90.         }
  91.  
  92.         for i = 1, #patterns do
  93.             local code = paste:match( patterns[i] )
  94.             if code then return code end
  95.         end
  96.  
  97.         return nil
  98.     end
  99.  
  100.     local get = function (url)
  101.         local paste = extractId( url )
  102.         if not paste then
  103.             error( "Invalid pastebin code.", 0 )
  104.             return
  105.         end
  106.  
  107.         -- Add a cache buster so that spam protection is re-checked
  108.         local cacheBuster = ("%x"):format(math.random(0, 2^30))
  109.         local response, err = http.get(
  110.             "https://pastebin.com/raw/"..textutils.urlEncode( paste ).."?cb="..cacheBuster
  111.         )
  112.  
  113.         if response then
  114.             -- If spam protection is activated, we get redirected to /paste with Content-Type: text/html
  115.             local headers = response.getResponseHeaders()
  116.             if not headers["Content-Type"] or not headers["Content-Type"]:find( "^text/plain" ) then
  117.                 error( "Pastebin blocked the download due to spam protection. Please complete the captcha in a web browser: https://pastebin.com/" .. textutils.urlEncode( paste ) , 0)
  118.                 return
  119.             end
  120.  
  121.             local sResponse = response.readAll()
  122.             response.close()
  123.             return sResponse
  124.         else
  125.             error (err, 0)
  126.         end
  127.     end
  128.  
  129.     -- Determine file to download
  130.     local sCode = paste
  131.     local sPath = path
  132.     if fs.exists( sPath ) then
  133.         error( "File already exists", 0 )
  134.         return
  135.     end
  136.  
  137.     -- GET the contents from pastebin
  138.     local res = get(sCode)
  139.     if res then
  140.         local file = fs.open( sPath, "w" )
  141.         file.write( res )
  142.         file.close()
  143.     end
  144. end
  145. local function wGet(url, sPath)
  146.  
  147.     if not http then
  148.         error( "HTTP disabled", 2 )
  149.     end
  150.  
  151.     local function getFilename( sUrl )
  152.         sUrl = sUrl:gsub( "[#?].*" , "" ):gsub( "/+$" , "" )
  153.         return sUrl:match( "/([^/]+)$" )
  154.     end
  155.  
  156.     local function get( sUrl )
  157.         -- Check if the URL is valid
  158.         local ok, err = http.checkURL( url )
  159.         if not ok then
  160.             error( err or "Invalid URL.", 0 )
  161.             return
  162.         end
  163.  
  164.         local response = http.get( sUrl , nil , true )
  165.         if not response then
  166.             error( "Failed." )
  167.         end
  168.  
  169.         local sResponse = response.readAll()
  170.         response.close()
  171.         return sResponse
  172.     end
  173.  
  174.     if fs.exists( sPath ) then
  175.         error( "File already exists", 0 )
  176.         return
  177.     end
  178.  
  179.     local res = get(url)
  180.     if not res then return end
  181.  
  182.     local file = fs.open( sPath, "wb" )
  183.     file.write( res )
  184.     file.close()
  185. end
  186.  
  187.  
  188. local function program()
  189.  
  190. --api loading
  191. if not surface then
  192.     if fs.exists(surfacepath) then
  193.         os.loadAPI(surfacepath)
  194.     else
  195.         print("installing missing component: " .. surfacepath)
  196.         pastebinGet("J2Y288mW", surfacepath)
  197.         os.loadAPI(surfacepath)
  198.     end
  199. end
  200. --term args
  201. local home --the directory Lattix is opened in
  202. local root --the root directory that you're not allowed to go above
  203.  
  204. if #tArgs > 0 then
  205.     home = tArgs[1]
  206. else
  207.     home = ""
  208. end
  209. if #tArgs > 1 then
  210.     root = tArgs[2]
  211. else
  212.     root = ""
  213. end
  214.  
  215. root = fs.combine("", root)
  216. home = fs.combine("", home)
  217.  
  218. --variables
  219. local clipboard --a file path/table of files
  220. local clip_cut = false -- original file(s) will be deleted if this is true
  221. local history = {}
  222. local w,h = term.getSize()
  223.  
  224.  
  225.  
  226. --setting up path
  227. if not term.isColor() then
  228.     lat_theme = lat_basic_theme
  229.     dirprefix = basic_dirprefix
  230.     selectprefix = basic_selectprefix
  231. end
  232. local path = root
  233.  
  234. if fs.isDir(fs.combine(root,home)) then
  235.     path = fs.combine(root,home)
  236. elseif fs.isDir(root) then
  237.     path = root
  238. else
  239.     error("Not valid root folder",0)
  240. end
  241. local scroll = 0
  242. local selected = 0
  243. local endprogram = false
  244. local isCtrlDown = false
  245. local isShiftDown = false
  246. local selection = {}
  247. local isMultiSelect = false
  248.  
  249. local index = 0
  250. local empty = function() end
  251. local shell_input = ""
  252.  
  253. local itemNames = {} --displayed texts
  254. local itemPaths = {} --paths to items
  255. local customTitle --if not nil then the title of the program is replaced with this
  256.  
  257.  
  258. --setting up the second session
  259. local alt_histories = {
  260. {path}, {path}, {path}, {path}
  261. }
  262. local alt_paths = {
  263.     [1] = path,
  264.     [2] = path,
  265.     [3] = path,
  266.     [4] = path
  267. }
  268. local alt_scrolls = {
  269.     0,0,0,0
  270. }
  271. local alt_selected = {
  272.     0,0,0,0
  273. }
  274. local cSession = 1
  275.  
  276. local surf = surface.create(w,h," ",lat_theme.blank_back, lat_theme.blank_text)
  277.  
  278. local pathValid = true
  279.  
  280. --functions yet undefined:
  281. local redraw --so that any gui drawing can call a redraw
  282. local remap --so that redraw and remap can be overwritten if neccessary
  283. local restore --so that special popups that use the main loop can restore the file browser functionality
  284. --helpers
  285. local function mathdown(num)
  286.     if num % 1 ~= 0 then
  287.         return math.floor(num)
  288.     end
  289.     return num - 1
  290. end
  291. local function sort(path) --returns a sorted table of folder names and file names at path with folders being first
  292.     local tbl = {}
  293.     items = fs.list(path)
  294.     table.sort(items)
  295.     --insert dirs
  296.     for i=1,#items do
  297.         if fs.isDir(fs.combine(path,items[i])) then
  298.             tbl[#tbl+1] = fs.combine(path, items[i])
  299.         end
  300.     end
  301.     for i=1,#items do
  302.         if not fs.isDir(fs.combine(path,items[i])) then
  303.             tbl[#tbl+1] = fs.combine(path, items[i])
  304.         end
  305.     end
  306.     return tbl
  307. end
  308. local function formatNumber(num)
  309.     local extra = {"B","KB","MB"}
  310.     local eindex = 1
  311.     while num > 1024 do
  312.         num = num / 1024
  313.         num = math.floor(num)
  314.         eindex = eindex + 1
  315.     end
  316.     return tostring(num..extra[eindex])
  317. end
  318. local function posToIndex(maxn,x,y,scroll)
  319.     return y+scroll-2 < maxn and y+scroll-1 or 0
  320. end
  321. local function IndexToPos(index,scroll)
  322.     return 2,index-scroll
  323. end
  324. local function clear()
  325.     surf:clear()
  326.     surf:render()
  327.     term.setCursorPos(1,1)
  328. end
  329.  
  330. local function readFolderRaw(folder)
  331.     customTitle = nil --to quit all special view modes
  332.     pathValid = true
  333.     path = folder
  334.     itemNames = {}
  335.     itemPaths = {}
  336.    
  337.     local t = sort(folder)
  338.     for i=1,#t do
  339.         itemNames[i] = fs.getName(t[i])
  340.         itemPaths[i] = t[i]
  341.     end
  342.    
  343. end
  344.  
  345. local function switchFolderRaw(folder) --folder is in absolute path, does not add to history
  346.     readFolderRaw(folder)
  347.     selected = 0
  348.     scroll = 0
  349.     shell_input = ""
  350. end
  351.  
  352. local function switchFolder(folder) --folder is in absolute path
  353.     history[#history + 1] = path
  354.     switchFolderRaw(folder)
  355. end
  356.  
  357. local function waitForKeyOrClick()
  358.     while true do
  359.         local e = os.pullEvent()
  360.         if e == "key" or e == "mouse_click" then
  361.             break
  362.         end
  363.     end
  364. end
  365. local function split(inputstr, sep)
  366.         if sep == nil then
  367.                 sep = "%s"
  368.         end
  369.         local t={} ; i=1
  370.         for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
  371.                 t[i] = str
  372.                 i = i + 1
  373.         end
  374.         return t
  375. end
  376. local function switchSessions(newI)
  377.     --if newI == cSession then return end
  378.     alt_histories[cSession] = history
  379.     alt_paths[cSession] = path
  380.     alt_scrolls[cSession] = scroll
  381.     alt_selected[cSession] = selected
  382.     history = alt_histories[newI]
  383.     --after all we need to update the item list
  384.     switchFolderRaw(alt_paths[newI])
  385.     scroll = alt_scrolls[newI]
  386.     selected = alt_selected[newI]
  387.     cSession = newI
  388. end
  389. local function extensionRead(extension, width, bcolor, tcolor, dtext)
  390.     local x,y = term.getCursorPos()
  391.     term.setCursorBlink(true)
  392.     local t = dtext or ""
  393.     local i = #t
  394.     local scroll = 0
  395.     local tbasis
  396.    
  397.     while true do
  398.         surf:drawLine(x,y,x+width,y," ", bcolor,tcolor)
  399.         tbasis = t .. extension
  400.         if i < scroll then
  401.             scroll = i
  402.         end
  403.         if i > scroll + width - #extension then
  404.             scroll = i - width + #extension
  405.         end
  406.         if #tbasis > width then
  407.             if scroll + width > #tbasis then
  408.                 scroll = #tbasis - width
  409.             end
  410.             tbasis = tbasis:sub(1+scroll, width+scroll)
  411.         else
  412.             scroll = 0
  413.         end
  414.         surf:drawText(x,y,tbasis,bcolor,tcolor)
  415.         surf:render()
  416.         term.setCursorPos(x+i-scroll, y)
  417.         local ev = {os.pullEvent()}
  418.         local e,k = ev[1], ev[2]
  419.         repeat
  420.             if e == "paste" then
  421.                 e = "char"
  422.                 table.remove(ev, 1)
  423.                 k = table.concat(ev, " ")
  424.             end
  425.             if e == "char" then
  426.                 if i == #t then
  427.                     t = t .. k
  428.                     i = i + #k
  429.                 elseif i == 0 then
  430.                     t = k .. t
  431.                     i = i + #k
  432.                 else
  433.                     t = t:sub(1,i) .. k .. t:sub(i+1, #t)
  434.                     i = i + #k
  435.                 end
  436.             end
  437.            
  438.             if e == "key" then
  439.                 local mini = 0
  440.                 local maxi = #t
  441.                 if k == keys.left and i > mini then
  442.                     i = i - 1
  443.                     break
  444.                 end
  445.                 if k == keys.right and i < maxi then
  446.                     i = i + 1
  447.                     break
  448.                 end
  449.                
  450.                 if k == keys.up then
  451.                     i = mini
  452.                     break
  453.                 end
  454.                 if k == keys.down then
  455.                     i = maxi
  456.                     break
  457.                 end
  458.                 if k == keys.enter then
  459.                     term.setCursorBlink(false)
  460.                     return t .. extension
  461.                 end
  462.                 if k == keys.delete and i < #t then
  463.                     t = t:sub(1,i) .. t:sub(i+2,#t)
  464.                     break
  465.                 end
  466.                 if k == keys.backspace and i > 0 then
  467.                     t = t:sub(1,i-1) .. t:sub(i+1, #t)
  468.                     i = i - 1
  469.                     break
  470.                 end
  471.             end
  472.         until true
  473.     end
  474. end
  475. --custom dialoges (just the GUI, it also draws it)
  476. local function drawInfobox(txt) --just info text, no buttons
  477.     surf:fillRect(math.floor(w/2-(#txt+2)/2), math.floor(h/2)-2, math.floor(w/2+(#txt+2)/2),math.floor(h/2)+2, " ", lat_theme.menu_back, lat_theme.menu_text)
  478.     surf:drawText(math.floor(w/2-(#txt+2)/2)+1,math.floor(h/2)-1, txt, lat_theme.menu_back, lat_theme.menu_text)
  479.     surf:render()
  480. end
  481. local function drawTextbox(txt) --text feedback
  482.     surf:fillRect(math.floor(w/2-(#txt+2)/2),math.floor(h/2)-2,math.floor(w/2+(#txt+2)/2),math.floor(h/2)+2, " ", lat_theme.menu_back, lat_theme.menu_text)
  483.     surf:drawText(math.floor(w/2-(#txt+2)/2)+1,math.floor(h/2)-1, txt, lat_theme.menu_back, lat_theme.menu_text)
  484.     surf:render()
  485.     term.setCursorPos(math.floor(w/2-(#txt+2)/2)+1,math.floor(h/2)+1)
  486. end
  487. local function drawButtonBox(txt,buttons) --multiple buttons, configurable
  488.     if type(buttons) ~= "table" then
  489.         buttons = {{" Yes ",colors.red,colors.white},{" No ",colors.gray,colors.white}}
  490.     end
  491.     if txt == "" or txt == nil then
  492.         txt = " Are you sure? "
  493.     end
  494.    
  495.     drawTextbox(txt) --reuse the rectangle
  496.     local x,y = term.getCursorPos()
  497.    
  498.     for i=1,#buttons do
  499.         x = math.floor((w/2-(#txt+2)/2)+((#txt+2)/i)*(i-1)) + 1
  500.         if term.isColor() then
  501.             surf:drawText(x,y, buttons[i][1], buttons[i][2], buttons[i][3])
  502.         else
  503.             surf:drawText(x,y,buttons[i][1], lat_theme.main_back, lat_theme.main_text)
  504.         end
  505.     end
  506.     surf:render()
  507.     return y
  508. end
  509. local function drawPopupBox(x,y,buttons) --x and y are the corners
  510.     local ydir = y < h/2 and 1 or -1
  511.     surf:fillRect(x,y,x+15,y+(#buttons+1)*ydir, " ", lat_theme.menu_back, lat_theme.menu_text)
  512.     for k,v in pairs(buttons) do
  513.         surf:drawText(x+1,y+k*ydir, v, lat_theme.menu_back, lat_theme.menu_text)
  514.     end
  515.     surf:render()
  516. end
  517. --custom dialogue functionality (difference from popups is that they stop the main program flow, program cannot close, etc. they are allowed to handle events)
  518. local function infoBox(txt, noredraw)
  519.     drawInfobox(txt)
  520.     waitForKeyOrClick()
  521.     if not noredraw then
  522.         redraw()
  523.     end
  524. end
  525. local function textBox(txt, dtext)
  526.     drawTextbox(txt)
  527.     local resp = extensionRead("", #txt, lat_theme.menu_back, lat_theme.menu_text, dtext)
  528.     redraw()
  529.     return resp
  530. end
  531. local function xtensionTextBox(txt, xts)
  532.     drawTextbox(txt)
  533.     local resp = extensionRead(xts, #txt, lat_theme.menu_back, lat_theme.menu_text)
  534.     redraw()
  535.     return resp
  536. end
  537. local function fileChooseBox(txt)
  538.     return textBox(txt)
  539. end
  540. local function dirChooseBox(txt)
  541.     return textBox(txt)
  542. end
  543. local function buttonBox(txt, buttons)
  544.     local y = drawButtonBox(txt, buttons)
  545.     while true do
  546.         local _,b,x2,y2 = os.pullEvent("mouse_click")
  547.         if b == 1 and y == y2 then
  548.             for i=1,#buttons do
  549.                 local x = math.floor((w/2-(#txt+2)/2)+((#txt+2)/i)*(i-1)) + 1
  550.                 if x2 > x - 1 and x2 < x + #buttons[i][1] then
  551.                     redraw()
  552.                     return i
  553.                 end
  554.             end
  555.         end
  556.     end
  557. end
  558. --these were moved above popupbox:
  559. local function safeRun(func, ...)
  560.     local succ, msg = pcall(func, ...)
  561.     if not succ then
  562.         infoBox(msg)
  563.     end
  564. end
  565. local function refresh()
  566.     if pathValid then
  567.         readFolderRaw(path)
  568.     else
  569.         local t = {}
  570.         for k,v in pairs(itemPaths) do
  571.             if fs.exists(v) then
  572.                 t[#t+1] = v
  573.             end
  574.         end
  575.         itemPaths = t
  576.     end
  577. end
  578. local function popupBox(x,y,buttons,functions) --popups defined to have width 15
  579.     drawPopupBox(x,y,buttons)
  580.     local ydir = y < h/2 and 1 or -1
  581.     while true do
  582.         local _,b,cx,cy = os.pullEvent("mouse_click")
  583.         if b == 1 then
  584.             if cx < x or cx > x + 15 then
  585.                 os.queueEvent("mouse_click", b, cx, cy)
  586.                 break
  587.             end
  588.             if not (cy*ydir > y*ydir and cy*ydir - y*ydir < #buttons+1) then
  589.                 os.queueEvent("mouse_click", b, cx, cy)
  590.                 break
  591.             end
  592.             --for menus inside popup boxes
  593.             redraw()
  594.             safeRun(functions[ydir*cy-ydir*y],x,y)
  595.             refresh()
  596.             break
  597.         else
  598.             os.queueEvent("mouse_click", b, cx, cy)
  599.             break
  600.         end
  601.     end
  602.    
  603. end
  604.  
  605. local findFileBox --for later
  606. local findDirBox
  607.  
  608. --file editing functionality
  609. local function run(path, ...)
  610.     local tArgs = {...}
  611.     local function box()
  612.         clear()
  613.        
  614.         shell.run(fs.combine("/", path), unpack(tArgs)) --better alternative to shell.run?
  615.         print("Press any key or click to return to Lattix")
  616.         waitForKeyOrClick()
  617.     end
  618.     local cor = coroutine.create(box)
  619.     coroutine.resume(cor)
  620.     --later better sandboxing required that doesn't allow modification of _G or some file defense
  621.     while (coroutine.status(cor) ~= "dead") do
  622.         local event = {os.pullEventRaw()} --terminate works only on the sub-program
  623.         coroutine.resume(cor, unpack(event))
  624.     end
  625. end
  626. local function mkdir(path, name)
  627.     fs.makeDir(fs.combine(path, name))
  628. end
  629. local function paste(path, clipboard, cutEnabled) --i wonder what happens if you try to move something inside of itself
  630.     if type(clipboard) == "table" then
  631.         for i=1,#clipboard do
  632.             paste(path, clipboard[i], cutEnabled)
  633.         end
  634.         return
  635.     end
  636.     if not clipboard then error("Clipboard is empty",0) end
  637.     if not fs.exists(clipboard) then error("File copied doesn't exist",0) end
  638.     local func = cutEnabled and fs.move or fs.copy
  639.     local goal = fs.combine(path, fs.getName(clipboard))
  640.     local i = 1
  641.     while fs.exists(goal) do
  642.         goal = fs.combine(path, "Copy" .. (i>1 and tostring(i) or "") .. " of " .. fs.getName(clipboard))
  643.         i = i + 1
  644.     end
  645.     func(clipboard, goal)
  646. end
  647. local function advFind(path, wildcard, results)
  648.     if not results then results = {} end
  649.     local t = fs.find(fs.combine(path, wildcard))
  650.     for i=1,#t do
  651.         results[#results+1] = t[i]
  652.     end
  653.     local dirs = fs.list(path)
  654.     for i=1,#dirs do
  655.         if fs.isDir(fs.combine(path, dirs[i])) then
  656.             results = advFind(fs.combine(path, dirs[i]), wildcard, results)
  657.         end
  658.     end
  659.    
  660.     return results
  661. end
  662. --GUI core functionality
  663.  
  664. local function doubleClick(path) --contains a reference to refresh
  665.     selected = 0
  666.     if fs.isDir(path) then
  667.         switchFolder(path)
  668.     else
  669.         local elements = split(fs.getName(path), ".")
  670.         if #elements == 1 then
  671.             elements[2] = "" --no extension
  672.         end
  673.         if not defaultPrograms[elements[#elements]] then
  674.             elements[#elements] = "def" --unknown extension
  675.         end
  676.         if (defaultPrograms[elements[#elements]]) == "api" then
  677.             os.loadAPI(path)
  678.             refresh()
  679.         elseif defaultPrograms[elements[#elements]] == "run" then
  680.             run(path)
  681.             refresh()
  682.         else
  683.             shell.run(defaultPrograms[elements[#elements]], path)
  684.             refresh()
  685.         end
  686.     end
  687. end
  688. local function gotoFolder()
  689.     local target
  690.         target = textBox("Please specify target folder")
  691.         if target == "" then return end --cancel if empty
  692.         if not target or not fs.exists(target) or not fs.isDir(target) then
  693.             infoBox("    Not a valid directory   ")
  694.             return
  695.         end
  696.         switchFolder(fs.combine("", target))
  697. end
  698. local function makeDirPrompt()
  699.     local name = textBox("Enter the name of the new directory")
  700.     if name == "" then return end
  701.     if fs.exists(fs.combine(path, name)) then
  702.         infoBox("Failure - File name already used")
  703.     end
  704.     mkdir(path, name)
  705. end
  706. local function pastebin()
  707.     local link = textBox("Enter the pastebin link")
  708.     if link == "" then return end
  709.     local name = textBox("Enter the name of the file")
  710.     if name == "" then return end
  711.     if fs.exists(fs.combine(path,name)) then
  712.         infoBox("Failure - File name already used")
  713.         return
  714.     end
  715.     pastebinGet(link, fs.combine(path, name))
  716. end
  717. local function wgetPrompt()
  718.     local link = textBox("Enter the url")
  719.     if link == "" then return end
  720.     local name = textBox("Enter the name of the file")
  721.     if name == "" then return end
  722.     if fs.exists(fs.combine(path,name)) then
  723.         infoBox("Failure - File name already used")
  724.         return
  725.     end
  726.     wGet(link, fs.combine(path, name))
  727. end
  728. local function runPrompt(appath)
  729.     if not appath then
  730.         appath = fs.combine(path, textBox("Enter the name of the script"))
  731.     end
  732.     if not fs.exists(appath) then
  733.         infoBox("Script doesn't exist: " .. appath)
  734.         return
  735.     end
  736.     if fs.isDir(appath) then
  737.         infoBox("Cannot run a directory: " .. appath)
  738.         return
  739.     end
  740.     args = textBox("Please enter the arguments")
  741.     run(appath, split(args, " "))
  742. end
  743. local function copy(name)--name actually means full path
  744.     clip_cut = false
  745.     clipboard = name
  746. end
  747. local function cut(name) --name actually means full path here
  748.     copy(name)
  749.     clip_cut = true
  750. end
  751. local function renamePrompt(path)
  752.     if not fs.exists(path) then infoBox("Nothing to rename") return end
  753.     local name = textBox("Enter the new name", fs.getName(path))
  754.     if name == "" then return end
  755.     if fs.exists(fs.combine(fs.getDir(path), name)) then
  756.         infoBox("Failure - File already exists")
  757.         return
  758.     end
  759.     fs.move(path, fs.combine(fs.getDir(path), name))
  760. end
  761. local function deletePrompt(path)
  762.     if not fs.exists(path) then infoBox("Nothing to delete") return end
  763.     local response = buttonBox("Do you really want to delete " .. fs.getName(path) .. "?", {{" Delete ", colors.white, colors.red}, {" Cancel ", colors.yellow, colors.black }})
  764.     if response == 1 then
  765.         fs.delete(path)
  766.     end
  767. end
  768. local function findFilesPrompt()
  769.     local wildCard = textBox("Enter filename or part of it")
  770.     if wildCard == "" then return end --cancel option
  771.     local finds = advFind(path, "*" .. wildCard .. "*")
  772.     if #finds == 0 then
  773.         infoBox("No files found")
  774.         return
  775.     end
  776.     itemNames = {}
  777.     itemPaths = {}
  778.     for i=1,#finds do
  779.         itemNames[i] = finds[i]
  780.         itemPaths[i] = finds[i]
  781.         customTitle = "Search results"
  782.         pathValid = false
  783.     end
  784.    
  785. end
  786. --GUI functionality - event mapping
  787. local buttons = {}
  788. local keymap = {}
  789. local eventmap = {}
  790.  
  791. for i=1,w do
  792.     buttons[i] = {}
  793.     for j=1,h do
  794.    
  795.         buttons[i][j] = {function() end, function() end}
  796.     end
  797. end
  798. local function newButton(x, y, w, h, func_left, func_right)
  799.     for i=x, x+w-1 do
  800.         for j=y, y+h-1 do
  801.             buttons[i][j] = {func_left, func_right}
  802.         end
  803.     end
  804. end
  805. local function clearButtons(x, y, w, h)
  806.     newButton(x,y,w,h, empty, empty)
  807. end
  808.  
  809. local function clearAllEvents()
  810.     keymap = {}
  811.     eventmap = {}
  812.     clearButtons(1,1,w,h)
  813. end
  814. local popup_newmenu_names = {
  815.     "New ...",
  816.     "New dir",
  817.     "Paste",
  818.     "Pastebin",
  819.     "wget"
  820. }
  821. local popup_newmenu_functions = {
  822.     function(x,y)
  823.         local options = {}
  824.         local functions = {}
  825.         for k,v in pairs(editors) do
  826.             local i = #options+1
  827.             options[i] = k
  828.             functions[i] = function()
  829.                 local ext = ""
  830.                 if editors_extensions[k] then
  831.                     ext = editors_extensions[k]
  832.                 end
  833.                 local app
  834.                 if v == "choose" then
  835.                    
  836.                 else
  837.                     app = v
  838.                 end
  839.                 local item = xtensionTextBox("What should the new file be called?", "." .. ext)
  840.                 local target = fs.combine(path, item)
  841.                 if not fs.exists(target) then
  842.                     shell.run(app, target)
  843.                 else
  844.                     infoBox("Failure - file already exists")
  845.                 end
  846.             end
  847.         end
  848.         popupBox(x,y,options,functions)
  849.     end,
  850.     function() makeDirPrompt() end,
  851.     function() paste(path, clipboard, clip_cut) end,
  852.     function() pastebin() end,
  853.     function() wgetPrompt() end,
  854.    
  855. }
  856. local popup_lockednewmenu_names = {
  857.     "Refresh"
  858. }
  859. local popup_lockednewmenu_functions = {
  860.     function() refresh() end
  861. }
  862.  
  863. local popup_menu_names = {
  864.     "Go to dir",
  865.     "Find file",
  866.     "Version: "..version
  867. }
  868. local popup_menu_functions = {
  869.     function() gotoFolder() end,
  870.     function() findFilesPrompt() end,
  871.     function() infoBox("Lattix version " .. version) end,
  872. }
  873. local filePopupNames = {
  874. --  "Edit", --opens default editor, as double click would, expect that for .lua it opens edit too
  875.     "Run", --runs
  876.     "Run w/ args", --runs with args
  877. --  "Open With", --select a program from a list and use it to open this, config file will be huge i see
  878.     "Rename",
  879.     "Copy",
  880.     "Cut",
  881.     "Delete"
  882. }
  883. local function getSelectedPath()
  884.     return itemPaths[selected]
  885. end
  886. local filePopupFunctions = {
  887.     function(x,y) run(getSelectedPath()) end,--run
  888.     function(x,y) runPrompt(getSelectedPath()) end,--run w args
  889.     function(x,y) renamePrompt(getSelectedPath()) end, --rename,
  890.     function(x,y) copy(getSelectedPath()) end, --copy
  891.     function(x,y) cut (getSelectedPath()) end, --cut
  892.     function(x,y) deletePrompt(getSelectedPath()) end --delete
  893. }
  894. local folderPopupNames = {
  895.     "Open",
  896. --  "Open in ...", --1,2,3,4 and program, TODO later bc open in and open with need some design decisions
  897. --  "Unpack",
  898.     "Rename",
  899.     "Copy",
  900.     "Cut",
  901.     "Delete"
  902. }
  903. local folderPopupFunctions = {
  904.     function(x,y) switchFolder(getSelectedPath()) end, --open
  905.     function(x,y) renamePrompt(getSelectedPath()) end, --rename
  906.     function(x,y) copy(getSelectedPath()) end, --copy
  907.     function(x,y) cut(getSelectedPath()) end, --cut
  908.     function(x,y) deletePrompt(getSelectedPath()) end --delete
  909. }
  910. local multiPopupNames = {}
  911. local multiPopupFunctions = {}--for multiselect, copy, cut, pack into folder, delete
  912.  
  913. local function mapMenu()
  914.     local back = function()
  915.         if not pathValid then
  916.             pathValid = true
  917.             refresh()
  918.            
  919.             return
  920.         end
  921.         if #history > 1 then
  922.             switchFolderRaw(history[#history])
  923.             table.remove(history, #history)
  924.         end
  925.     end
  926.     local up = function()
  927.         if not pathValid then
  928.             pathValid = true
  929.             refresh()
  930.             return
  931.         end
  932.         if path == "" or path == "/" then return end
  933.         switchFolder(fs.combine(path, ".."))
  934.     end
  935.     local menu = function()
  936.         --open advanced menu
  937.         popupBox(9,2,popup_menu_names, popup_menu_functions)
  938.     end
  939.     local root = function()
  940.         if not pathValid then
  941.             pathValid = true
  942.         end
  943.         switchFolder(root)
  944.     end
  945.     local plus = function()
  946.         --open new menu
  947.         if pathValid then
  948.             popupBox(5,2,popup_newmenu_names, popup_newmenu_functions)
  949.         else
  950.             popupBox(5,2,popup_lockednewmenu_names, popup_lockednewmenu_functions)
  951.         end
  952.     end
  953.     local quit = function()
  954.         clear()
  955.         endprogram = true
  956.     end
  957.     local switch = function(e)
  958.         switchSessions(e[3]-w+6)
  959.     end
  960.     newButton(1,1,1,1, back, empty)
  961.     newButton(3,1,1,1, up, empty)
  962.     newButton(7,1,1,1, root, empty)
  963.     newButton(5,1,1,1, plus, empty)
  964.     newButton(9,1,1,1, menu, empty)
  965.     newButton(w,1,1,1, quit, empty)
  966.     newButton(w-5,1,4,1, switch, empty)
  967.     keymap[keys.left] = up
  968. end
  969. local drawFiles
  970.  
  971. local function enterPress()
  972.     words = split(shell_input, " ")
  973.     if words and #words > 0 and commands[words[1]] then
  974.         commands[words[1]](words)
  975.     elseif selected > 0 and selected <= #itemPaths then
  976.         doubleClick(itemPaths[selected])
  977.     end
  978.     shell_input = ""
  979. end
  980. local function filePopup(x,y,path)
  981.     if fs.isDir(path) then
  982.         --directory
  983.         popupBox(x,y,folderPopupNames, folderPopupFunctions)
  984.     else
  985.         --file
  986.         popupBox(x,y,filePopupNames, filePopupFunctions)
  987.     end
  988. end
  989. local function mapFiles()
  990.     --popup menu implementation (right click)
  991.     local file_rightclick = function(e)
  992.         local i = posToIndex(#itemNames,e[3],e[4],scroll) --get index
  993.         if itemPaths[i] then
  994.             if selected ~= i then --select if not selected
  995.                 selected = i --just for aesthetics
  996.                 redraw()
  997.             end
  998.             --show file/folder/multiselect relevant popup
  999.             if selection and #selection > 0 then
  1000.                 --multiselect
  1001.             else
  1002.                 filePopup(e[3],e[4],getSelectedPath())
  1003.             end
  1004.         else
  1005.             selected = 0
  1006.             --show the same popup as the + button in the menu
  1007.             if pathValid then
  1008.                 popupBox(e[3],e[4],popup_newmenu_names, popup_newmenu_functions)
  1009.             else
  1010.                 popupBox(e[3], e[4], popup_lockednewmenu_names, popup_lockednewmenu_functions)
  1011.             end
  1012.         end
  1013.     end
  1014.     --select implementation (left click)
  1015.     local file_leftclick = function(e)
  1016.         local i = posToIndex(#itemNames,e[3],e[4],scroll)
  1017.         if itemPaths[i] then
  1018.             if selected == i then
  1019.                 doubleClick(itemPaths[i])
  1020.             else
  1021.                 selected = i
  1022.             end
  1023.         else
  1024.             selected = 0
  1025.         end
  1026.     end
  1027.    
  1028.     newButton(1,2,w,h-2,file_leftclick, file_rightclick)
  1029.     --multiselect stuff
  1030.    
  1031.     --scrolling
  1032.     eventmap["mouse_scroll"] = function(e)
  1033.         local b = e[2]
  1034.         if b == 1 and #itemPaths - scroll > h-2 then
  1035.             scroll = scroll + 1
  1036.         end
  1037.         if b == -1 and scroll > 0 then
  1038.             scroll = scroll - 1
  1039.         end
  1040.     end
  1041.    
  1042.     keymap[keys.enter] = enterPress
  1043.     keymap[keys.up] = function()
  1044.         if selected > 1 then
  1045.             selected = selected - 1
  1046.             if scroll >= selected then
  1047.                 scroll = selected - 1
  1048.             end
  1049.         end
  1050.         if selected == 0 then
  1051.             selected = #itemPaths
  1052.             if scroll + h - 2 < selected then
  1053.                 scroll = selected - h + 2
  1054.             end
  1055.         end
  1056.     end
  1057.     keymap[keys.down] = function()
  1058.         if selected < #itemPaths then
  1059.             selected = selected + 1
  1060.             if scroll + h - 2 < selected then
  1061.                 scroll = selected - h + 2
  1062.             end
  1063.         end
  1064.     end
  1065.     keymap[keys.right] = function()
  1066.         if selected > 0 and selected <= #itemPaths then
  1067.             local x,y = IndexToPos(selected, scroll)
  1068.             if x < 2 then
  1069.                 x = 2
  1070.             end
  1071.             if x > h-1 then
  1072.                 x = h-1
  1073.             end
  1074.             filePopup(x,y, getSelectedPath())
  1075.         end
  1076.     end
  1077. end
  1078. local commands = { --table of functions, arg: list of words typed, including the command itself
  1079.     ["run"] = function(words)
  1080.         if #words == 1 then
  1081.             runPrompt()
  1082.             return
  1083.         end
  1084.         local path = fs.combine(path, words[2])
  1085.         local args = {}
  1086.         if #words > 2 then
  1087.             for i=3, #words do
  1088.                 args[i-2] = words[i]
  1089.             end
  1090.         end
  1091.         if fs.exists(path) and not fs.isDir(path) then
  1092.             run(path, unpack(args))
  1093.         end
  1094.     end,
  1095.     ["goto"] = function(words)
  1096.         local target
  1097.         if #words < 2 then
  1098.             gotoFolder()
  1099.             return
  1100.         else
  1101.             target = words[2]
  1102.         end
  1103.         if not target or not fs.exists(target) or not fs.isDir(target) then
  1104.             infoBox("    Not a valid directory   ")
  1105.             return
  1106.         end
  1107.         switchFolder(fs.combine("", target))
  1108.     end
  1109. }
  1110. local function mapBar()
  1111.     eventmap.char = function(e)
  1112.         shell_input = shell_input .. e[2]
  1113.     end
  1114.     keymap[keys.backspace] = function()
  1115.         shell_input = shell_input:sub(1, #shell_input-1)
  1116.     end
  1117.     --enter is mapped in mapFiles for the bar too
  1118. end
  1119. --draw components
  1120. local function drawMenu()
  1121.     surf:drawLine(1,1,w,1," ", lat_theme.menu_back, lat_theme.menu_text)
  1122.     if term.isColor() then
  1123.         surf:drawText(1,1,"< ^ + / m", lat_theme.menu_back, lat_theme.menu_text)
  1124.     end
  1125.     local str
  1126.     if customTitle then
  1127.         str = customTitle
  1128.     else
  1129.         if path ~= "" then
  1130.             str = cSession .. " - " .. path
  1131.         else
  1132.             str = tostring(cSession)
  1133.         end
  1134.     end
  1135.     str = #str < w/2 and str or str:sub(1,math.floor(w/2)) .. "..."
  1136.     surf:drawText(8+math.floor((w-12)/2-#str/2),1,str, lat_theme.menu_back, lat_theme.menu_text)
  1137.    
  1138.     if term.isColor() then
  1139.         surf:drawText(w-5,1,"1234", lat_theme.menu_back, lat_theme.menu_text)
  1140.         surf:drawPixel(w,1,"x", colors.red, colors.white)
  1141.     end
  1142.    
  1143. end
  1144.  
  1145. drawFiles = function()
  1146.     if selection == nil then selection = {} end --just in case selection was nilled
  1147.     local cy = 2 --current y pos of drawing
  1148.     local i = scroll + 1 --index in "items"
  1149.     local tcol
  1150.     local bcol
  1151.     while IndexToPos(i,scroll) < h do
  1152.         if not itemNames[i] then break end --because the while condition checks for screen size, this checks for file count
  1153.         local twrite = ""
  1154.         bcol = (lat_theme.main_back)
  1155.         if selection[i] then
  1156.             bcol = (lat_theme.multiselect_back)
  1157.             twrite = twrite .. selectprefix
  1158.         end
  1159.         if i ~= selected then
  1160.             if not fs.isDir(itemPaths[i]) then
  1161.                 tcol = (lat_theme.main_text)
  1162.             else
  1163.                 tcol = (lat_theme.dir_text)
  1164.                
  1165.             end
  1166.         else
  1167.             tcol = (lat_theme.selected_text)
  1168.         end
  1169.         if fs.isDir(itemPaths[i]) then
  1170.             twrite = twrite .. dirprefix
  1171.         end
  1172.         twrite = twrite .. itemNames[i]
  1173.         surf:drawLine(1, cy, w, cy, " ", bcol, tcol)
  1174.         surf:drawText(2, cy, twrite, bcol, tcol)
  1175.         local mwrite = "-"
  1176.         if not fs.isDir(itemPaths[i]) then
  1177.             mwrite = formatNumber(fs.getSize(itemPaths[i]))
  1178.         end
  1179.         local startX = w-6
  1180.         surf:drawLine(startX-1, cy, w, cy, " ", bcol, tcol)
  1181.         surf:drawText(startX,cy, mwrite, bcol, tcol)
  1182.         i = i + 1
  1183.         cy = cy + 1 --up both indexes
  1184.     end
  1185.     return itemNames
  1186. end
  1187.  
  1188. local function drawBar()
  1189.     surf:drawLine(1,h,w,h, " ", lat_theme.menu_back, lat_theme.menu_text)
  1190.     surf:drawText(1,h, path .. "> " .. shell_input, lat_theme.menu_back, lat_theme.menu_text)
  1191. end
  1192.  
  1193.  
  1194. --GUI drawing functions
  1195. redraw = function()
  1196.     if not fs.exists(path) or not fs.isDir(path) then
  1197.         infoBox("Error: " .. path .. " is not a valid directory", true)
  1198.         path = root
  1199.     end
  1200.    
  1201.     surf:clear(" ", lat_theme.blank_back, lat_theme.blank_text)
  1202.     drawMenu()
  1203.    
  1204.     drawFiles()
  1205.    
  1206.     drawBar()
  1207.    
  1208.     surf:render()
  1209. end
  1210.  
  1211. remap = function()
  1212.     clearAllEvents()
  1213.     mapMenu()
  1214.     mapFiles()
  1215.     mapBar()
  1216. end
  1217. local oRedraw = redraw --orginal backup
  1218. local oRemap = remap
  1219. restore = function()
  1220.     redraw = oRedraw
  1221.     remap = oRemap
  1222. end
  1223.  
  1224. do --choose file/folder dialog
  1225.  
  1226. end
  1227.  
  1228. switchFolder(path)
  1229.  
  1230. --main loop
  1231. while not endprogram do
  1232.     redraw()
  1233.     remap()
  1234.     local e = { os.pullEvent() }
  1235.     if e[1] == "mouse_click" then
  1236.         buttons[e[3]][e[4]][e[2]](e)
  1237.     elseif e[1] == "key" then
  1238.         if keymap[e[2]] then
  1239.             keymap[e[2]](e)
  1240.         end
  1241.     elseif eventmap[e[1]] then
  1242.         eventmap[e[1]](e)
  1243.     end
  1244. end
  1245.  
  1246. end
  1247.  
  1248. local succ, msg = pcall(program)
  1249.  
  1250. term.setBackgroundColor(colors.black)
  1251. term.setTextColor(colors.white)
  1252. term.clear()
  1253. term.setCursorPos(1,1)
  1254.    
  1255. if not succ and msg ~= "Terminated" then
  1256.     print("Congratulations, The file browser has crashed.")
  1257.     print()
  1258.     print("Please report to a RobCo Technical Help Facility.")
  1259.     print()
  1260.     print(msg)
  1261. end
  1262. if succ or msg == "Terminated" then
  1263.  
  1264. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement