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