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