· 2 years ago · Dec 15, 2022, 09:20 PM
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
10local tArgs = { "/R_Files", "/R_Files"}
11local version = "a0.2"
12
13--CONFIG
14--colors of lattix
15local 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
27local 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}
38local dirprefix = "Terminal/" --this text gets added before dirs
39local basic_dirprefix = "[] " --this text gets added before dirs on non-advanced computers
40local selectprefix = "" --this text gets added before selected things (even in multiselect)
41local 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
44local 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
56local 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
66local editors_extensions = {
67 ["NFP painting"] = "nfp",
68 ["Lua script"] = "lua",
69 ["Text file"] = "txt"
70
71
72}
73
74local surfacepath = "surface"
75
76--code adopted and modified from CC's source code
77local 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
144end
145local 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()
185end
186
187
188local function program()
189
190--api loading
191if 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
199end
200--term args
201local home --the directory Lattix is opened in
202local root --the root directory that you're not allowed to go above
203
204if #tArgs > 0 then
205 home = tArgs[1]
206else
207 home = ""
208end
209if #tArgs > 1 then
210 root = tArgs[2]
211else
212 root = ""
213end
214
215root = fs.combine("", root)
216home = fs.combine("", home)
217
218--variables
219local clipboard --a file path/table of files
220local clip_cut = false -- original file(s) will be deleted if this is true
221local history = {}
222local w,h = term.getSize()
223
224
225
226--setting up path
227if not term.isColor() then
228 lat_theme = lat_basic_theme
229 dirprefix = basic_dirprefix
230 selectprefix = basic_selectprefix
231end
232local path = root
233
234if fs.isDir(fs.combine(root,home)) then
235 path = fs.combine(root,home)
236elseif fs.isDir(root) then
237 path = root
238else
239 error("Not valid root folder",0)
240end
241local scroll = 0
242local selected = 0
243local endprogram = false
244local isCtrlDown = false
245local isShiftDown = false
246local selection = {}
247local isMultiSelect = false
248
249local index = 0
250local empty = function() end
251local shell_input = ""
252
253local itemNames = {} --displayed texts
254local itemPaths = {} --paths to items
255local customTitle --if not nil then the title of the program is replaced with this
256
257
258--setting up the second session
259local alt_histories = {
260{path}, {path}, {path}, {path}
261}
262local alt_paths = {
263 [1] = path,
264 [2] = path,
265 [3] = path,
266 [4] = path
267}
268local alt_scrolls = {
269 0,0,0,0
270}
271local alt_selected = {
272 0,0,0,0
273}
274local cSession = 1
275
276local surf = surface.create(w,h," ",lat_theme.blank_back, lat_theme.blank_text)
277
278local pathValid = true
279
280--functions yet undefined:
281local redraw --so that any gui drawing can call a redraw
282local remap --so that redraw and remap can be overwritten if neccessary
283local restore --so that special popups that use the main loop can restore the file browser functionality
284--helpers
285local function mathdown(num)
286 if num % 1 ~= 0 then
287 return math.floor(num)
288 end
289 return num - 1
290end
291local 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
307end
308local 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])
317end
318local function posToIndex(maxn,x,y,scroll)
319 return y+scroll-2 < maxn and y+scroll-1 or 0
320end
321local function IndexToPos(index,scroll)
322 return 2,index-scroll
323end
324local function clear()
325 surf:clear()
326 surf:render()
327 term.setCursorPos(1,1)
328end
329
330local 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
343end
344
345local 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 = ""
350end
351
352local function switchFolder(folder) --folder is in absolute path
353 history[#history + 1] = path
354 switchFolderRaw(folder)
355end
356
357local 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
364end
365local 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
375end
376local 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
388end
389local 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
474end
475--custom dialoges (just the GUI, it also draws it)
476local 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()
480end
481local 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)
486end
487local 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
508end
509local 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()
516end
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)
518local function infoBox(txt, noredraw)
519 drawInfobox(txt)
520 waitForKeyOrClick()
521 if not noredraw then
522 redraw()
523 end
524end
525local 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
530end
531local 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
536end
537local function fileChooseBox(txt)
538 return textBox(txt)
539end
540local function dirChooseBox(txt)
541 return textBox(txt)
542end
543local 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
557end
558--these were moved above popupbox:
559local function safeRun(func, ...)
560 local succ, msg = pcall(func, ...)
561 if not succ then
562 infoBox(msg)
563 end
564end
565local 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
577end
578local 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
603end
604
605local findFileBox --for later
606local findDirBox
607
608--file editing functionality
609local 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
625end
626local function mkdir(path, name)
627 fs.makeDir(fs.combine(path, name))
628end
629local 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)
646end
647local 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
661end
662--GUI core functionality
663
664local 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
687end
688local 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))
697end
698local 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)
705end
706local 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))
716end
717local 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))
727end
728local 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, " "))
742end
743local function copy(name)--name actually means full path
744 clip_cut = false
745 clipboard = name
746end
747local function cut(name) --name actually means full path here
748 copy(name)
749 clip_cut = true
750end
751local 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))
760end
761local 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
767end
768local 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
785end
786--GUI functionality - event mapping
787local buttons = {}
788local keymap = {}
789local eventmap = {}
790
791for i=1,w do
792 buttons[i] = {}
793 for j=1,h do
794
795 buttons[i][j] = {function() end, function() end}
796 end
797end
798local 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
804end
805local function clearButtons(x, y, w, h)
806 newButton(x,y,w,h, empty, empty)
807end
808
809local function clearAllEvents()
810 keymap = {}
811 eventmap = {}
812 clearButtons(1,1,w,h)
813end
814local popup_newmenu_names = {
815 "New ...",
816 "New dir",
817 "Paste",
818 "Pastebin",
819 "wget"
820}
821local 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}
856local popup_lockednewmenu_names = {
857 "Refresh"
858}
859local popup_lockednewmenu_functions = {
860 function() refresh() end
861}
862
863local popup_menu_names = {
864 "Go to dir",
865 "Find file",
866 "Version: "..version
867}
868local popup_menu_functions = {
869 function() gotoFolder() end,
870 function() findFilesPrompt() end,
871 function() infoBox("Lattix version " .. version) end,
872}
873local 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}
883local function getSelectedPath()
884 return itemPaths[selected]
885end
886local 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}
894local 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}
903local 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}
910local multiPopupNames = {}
911local multiPopupFunctions = {}--for multiselect, copy, cut, pack into folder, delete
912
913local 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
968end
969local drawFiles
970
971local 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 = ""
979end
980local 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
988end
989local 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
1077end
1078local 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}
1110local 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
1118end
1119--draw components
1120local 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
1143end
1144
1145drawFiles = 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
1186end
1187
1188local 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)
1191end
1192
1193
1194--GUI drawing functions
1195redraw = 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()
1209end
1210
1211remap = function()
1212 clearAllEvents()
1213 mapMenu()
1214 mapFiles()
1215 mapBar()
1216end
1217local oRedraw = redraw --orginal backup
1218local oRemap = remap
1219restore = function()
1220 redraw = oRedraw
1221 remap = oRemap
1222end
1223
1224do --choose file/folder dialog
1225
1226end
1227
1228switchFolder(path)
1229
1230--main loop
1231while 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
1244end
1245
1246end
1247
1248local succ, msg = pcall(program)
1249
1250term.setBackgroundColor(colors.black)
1251term.setTextColor(colors.white)
1252term.clear()
1253term.setCursorPos(1,1)
1254
1255if 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)
1261end
1262if succ or msg == "Terminated" then
1263
1264end