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