· 4 years ago · Apr 04, 2021, 04:48 AM
1--[[
2HPWebcamAble Presents...
3File Manager
4
5=== Description ====
6This program adds a user interface for file browsing
7
8
9==== Documentation ====
10ComputerCraft Forum:
11http://www.computercraft.info/forums2/index.php?/topic/24579-file-manager-20-history-run-w-args-grayscale-support/
12
13Youtube Video:
14NOTE: This video is for Version 1.0, and is a little outdated now
15https://www.youtube.com/watch?v=pdaWStx-rwA
16
17
18==== Installation and Use ====
19Pastebin Code: jKZBPFTs
20
21pastebin get <code> fm
22
23Then run 'fm' (Or what you called it)
24
25
26==== Update History ====
27Pastebin will always have the most recent version
28
29|2.0.1| <- This program
30 -Fixed detection of grayscale computers in CC 1.76
31
32|2.0|
33 -Rewrite of almost entire program!
34 -Auto adjusts to display on almost any screen size
35 -Dipslay items as a list, or tiles
36 -Right click menu
37 -Click a selected item to open it
38 -Assign programs that should be used to run file endings
39 *For example: Use 'edit' to open .txt files
40 -Run programs with arguments (finally...)
41
42|Before 2.0|
43 -Old, but still on PB:
44 http://pastebin.com/uz2f7Xbe
45]]
46
47--=== Variables ===--
48local version = "2.0.1"
49local w,h = term.getSize()
50local settings = {
51 colors = {
52 ["Full Color"] = {
53 headingText = colors.white,
54 headingBack = colors.blue,
55 back = colors.white,
56 text = colors.black,
57 selected = colors.lightBlue,
58 selectedText = colors.white,
59 folder = colors.yellow,
60 folderText = colors.white,
61 file = colors.lightGray,
62 fileText = colors.white,
63 inputBack = colors.black,
64 inputText = colors.white,
65 menuBack = colors.gray,
66 menuButtons = { text = colors.black, textSelected = colors.white, default = colors.lime, cancel = colors.red, disabled = colors.lightGray},
67 },
68 ["Gray Scale"] = {
69 headingText = colors.white,
70 headingBack = colors.black,
71 back = colors.white,
72 text = colors.black,
73 selected = colors.lightGray,
74 selectedText = colors.white,
75 folder = colors.gray,
76 folderText = colors.white,
77 file = colors.lightGray,
78 fileText = colors.white,
79 inputBack = colors.black,
80 inputText = colors.white,
81 menuBack = colors.gray,
82 menuButtons = { text = colors.white, textSelected = colors.white, default = colors.black, cancel = colors.black, disabled = colors.black},
83 },
84 ["Black and White"] = {
85 -- WIP
86 }
87 }
88}
89local colorType
90
91local displayViews = { "List" , "Tiles" }
92local curView = displayViews[1]
93
94local showText
95
96--Directory stuff
97local curPath = {}
98local dir
99local pathFolders
100local pathFiles
101local selected
102local numSelect = 0
103local pathHistory = {{""}}
104local historyPos = 1
105
106--Page stuff
107local perPage = 0
108local pages = 0
109local page = 0
110
111--Screen constraints
112local minY = 3
113local maxY = h-1
114local minX = 1
115local maxX = w-1
116
117--Types of items (File Endings)
118local items = {
119 {
120 name = "File",
121 create = function(path)
122 local f = fs.open(path,"w")
123 if not f then return false end
124 f.close()
125 return true
126 end,
127 equals = function(name)
128 local temp = split(name,".")
129 return #temp < 2
130 end,
131 open = function(path)
132 runProgram(path)
133 end
134 },
135 {
136 name = "Folder",
137 create = function(path)
138 fs.makeDir(path)
139 return fs.exists(path) and fs.isDir(path)
140 end,
141 equals = function(path)
142 return fs.isDir(path)
143 end
144 }
145}
146local itemsByName = {}
147for i = 1, #items do
148 itemsByName[items[i].name] = i
149end
150
151--Key states
152local shiftHeld = false
153local ctrlHeld = false
154
155--List view variables
156local midPage
157
158--Screen API Vars
159local screens = {}
160local curScreen
161local default = {
162 object = {
163 test = "default",
164 name = "default",
165 minX = 1,
166 maxX = 7,
167 minY = 1,
168 maxY = 3,
169 colors = {
170 text = {
171 on = colors.white,
172 off = colors.white
173 },
174 back = {
175 on = colors.lime,
176 off = colors.red
177 }
178 },
179 hasClickArea = true,
180 state = true
181 --action isn't here, it isn't necesarry for the program to work
182 },
183 clickArea = {
184 name = "default",
185 minX = 1,
186 maxX = 5,
187 minY = 1,
188 maxY = 3,
189 state = true
190 --Again, action is left out
191 }
192}
193
194
195--=== Functions ===--
196local function printC(text,y,onlyCalc)--Prints text centered at y
197 y = tonumber(y)
198 if not y or not text then error("expected string,number got "..type(text)..","..type(y),2) end
199 local tLenght = #tostring(text)
200 local sStart = math.ceil(w/2-tLenght/2)
201 local sEnd = sStart + tLenght
202 term.setCursorPos(sStart,y)
203 term.write(text)
204 return sStart,sEnd
205end
206
207local function split(sString,sep)
208 if sep == nil then sep = "%s" end
209 local t={}
210 for str in string.gmatch(sString, "([^"..sep.."]+)") do
211 table.insert(t,str)
212 end
213 return t
214end
215
216local function scrollRead(x,y,nLength,insertText) --This is a simple scrolling-read function I made
217 if insertText then
218 insertText = tostring(insertText)
219 cPos = #insertText+1
220 cInput = insertText
221 else
222 cPos = 1
223 cInput = ""
224 end
225 term.setCursorBlink(true)
226 while true do
227 term.setCursorPos(x,y)
228 term.write(string.rep(" ",nLength))
229 term.setCursorPos(x,y)
230 if string.len(cInput) > nLength-1 then
231 term.write(string.sub(cInput,(nLength-1)*-1))
232 else
233 term.write(cInput)
234 end
235 if cPos > nLength-1 then
236 term.setCursorPos(x+nLength-1,y)
237 else
238 term.setCursorPos(x+cPos-1,y)
239 end
240 local event,p1 = os.pullEvent()
241 if event == "char" then
242 cInput = string.sub(cInput,1,cPos)..p1..string.sub(cInput,cPos+1)
243 cPos = cPos + 1
244 elseif event == "key" then
245 if p1 == keys.enter then
246 break
247 elseif p1 == keys.backspace then
248 if cPos > 1 then
249 cInput = string.sub(cInput,1,cPos-2)..string.sub(cInput,cPos)
250 cPos = cPos - 1
251 end
252 elseif p1 == keys["end"] then
253 cPos = string.len(cInput)+1
254 end
255 end
256 end
257 term.setCursorBlink(false)
258 return cInput
259end
260
261local bytesInKiloByte = 1024
262local function toKiloBytes(nBytes)
263 return math.ceil(nBytes / bytesInKiloByte)
264end
265
266local function getSize(path,shrink)
267 if not fs.exists(path) then return 0 end
268 if fs.isDir(path) then return 0 end
269 local size = fs.getSize(path)
270 if size == 0 then return "1 Byte" end
271 if size < bytesInKiloByte then return size.." Byte"..(size > 0 and "s" or "") end
272 return toKiloBytes(size)..(shrink and " KB" or " KiloBytes")
273end
274
275--Functions from my Screen API
276local function assert(statement,errorText,errorLevel)
277 if not statement then
278 error(errorText,errorLevel+1)
279 end
280end
281
282local function fillTable(toFill,fillWith) --Used by the API
283 for a,b in pairs(fillWith) do
284 if type(b) == "table" then
285 if not toFill then toFill = {} end
286 toFill[a] = fillTable(toFill[a],b)
287 else
288 if not toFill then toFill = {} end
289 toFill[a] = toFill[a] or b
290 end
291 end
292 return toFill
293end
294
295--Misc--
296function checkPos(x,y,screen)
297 screen = screen or curScreen
298 assert(screens[screen],"screen '"..screen.."' doesn't exsist",2)
299 x = tonumber(x)
300 y = tonumber(y)
301 assert(x and y,"expected number,number",2)
302 local insideArea = {}
303 for name,data in pairs(screens[screen].clickAreas) do
304 if x >= data.minX and x <= data.maxX and y >= data.minY and y <= data.maxY and data.state == true then
305 if data.action then
306 return data.action()
307 else
308 return "click_area",name
309 end
310 end
311 end
312 for name,data in pairs(screens[screen].objects) do
313 if data.hasClickArea and data.state ~= "off" and x >= data.minX and x <= data.maxX and y >= data.minY and y <= data.maxY then
314 if data.action then
315 return data.action()
316 else
317 return "object",name
318 end
319 end
320 end
321end
322
323function handleEvents(screen,useRaw)
324 screen = screen or curScreen
325 assert(screens[screen],"screen '"..screen.."' doesn't exsist",2)
326 local pull = os.pullEvent
327 if useRaw == true then
328 pull = os.pullEventRaw
329 end
330 local eArgs = {pull()}
331 if eArgs[1] == "mouse_click" then
332 local cType,name = checkPos(eArgs[3],eArgs[4],screen)
333 if type(name) == "string" then
334 return cType,name,eArgs[2]
335 end
336 end
337 return unpack(eArgs)
338end
339
340function setDefaultObject(newDefaultObject)
341 assert(type(newDefaultObject) == "table","expected table, got "..type(newDefaultObject),2)
342 newDefaultObject = fillTable(newDefaultObject,default.object)
343 default.object = newDefaultObject
344end
345
346function setDefaultClickArea(newDefaultClickArea)
347 assert(type(newDefaultClickArea) == "table","expected table, got "..type(newDefaultClickArea),2)
348 newDefaultClickArea = fillTable(newDefaultClickArea,default.clickArea)
349 default.clickArea = newDefaultClickArea
350end
351
352--Screens--
353function addScreen(name,backColor,setToCurScreen)
354 assert(name,"expected name",2)
355 assert(screens[name] == nil,"screen '"..name.."' already exsits",2)
356 screens[name] = {
357 background = backColor or colors.white,
358 objects = {},
359 clickAreas = {}
360 }
361 if setToCurScreen then curScreen = name end
362end
363
364function setScreen(name)
365 assert(screens[name] ~= nil,"screen doesn't exist",2)
366 curScreen = name
367end
368
369function draw(name,place)
370 name = name or curScreen
371 assert(screens[name] ~= nil,"screen doesn't exist",2)
372 place = place or term
373 assert(type(place) == "table","place should be a table, or nil",2)
374 place.setBackgroundColor(screens[name].background)
375 place.clear()
376 for objName,objData in pairs(screens[name].objects) do
377 if objData.state ~= "off" then
378 drawObject(objName,name,place)
379 end
380 end
381end
382
383function getScreen(name)
384 name = name or curScreen
385 assert(screens[name] ~= nil,"screen doesn't exist",2)
386 return screens[name]
387end
388
389--Click Areas--
390function addClickArea(clickAreaInfo,screen)
391 screen = screen or curScreen
392 assert(screens[screen] ~= nil,"screen doesn't exist",2)
393 assert(type(clickAreaInfo) == "table","expected table, got "..type(clickAreaInfo),2)
394 clickAreaInfo = fillTable(clickAreaInfo,default.clickArea)
395 assert( screens[screen].clickAreas[clickAreaInfo.name] == nil,"a click area with the name '"..clickAreaInfo.name.."' already exsists")
396 screens[screen].clickAreas[clickAreaInfo.name] = clickAreaInfo
397end
398
399function toggleClickArea(name,screen)
400 screen = screen or curScreen
401 assert(screens[screen] ~= nil,"screen doesn't exist",2)
402 assert(screens[screen].clickAreas[name] ~= nil,"Click Area '"..name.."' doesn't exsist",2)
403 screens[screen].clickAreas[name].state = not screens[screen].clickAreas[name].state
404 return screens[screen].clickAreas[name]
405end
406
407--Objects--
408function addObject(objectInfo,screen)
409 screen = screen or curScreen
410 assert(screens[screen] ~= nil,"screen '"..screen.."' doesn't exist",2)
411 assert(type(objectInfo) == "table","expected table, got "..type(objectInfo),2)
412 objectInfo = fillTable(objectInfo,default.object)
413 assert(screens[screen].objects[objectInfo.name] == nil,"an object with the name '"..objectInfo.name.."' already exsists")
414 screens[screen].objects[objectInfo.name] = objectInfo
415end
416
417function drawObject(name,screen,place)
418 screen = screen or curScreen
419 assert(screens[screen] ~= nil,"screen doesn't exist",2)
420 assert(screens[screen].objects[name] ~= nil,"Object '"..name.."' doesn't exsist",2)
421 place = place or term
422 assert(type(place) == "table","place should be a table, or nil",2)
423 local objData = screens[screen].objects[name]
424 assert(type(objData.state) == "boolean","Object '"..name.."' is off, and can't be drawn",2)
425 if objData.state == true then
426 place.setBackgroundColor(objData.colors.back.on)
427 place.setTextColor(objData.colors.text.on)
428 else
429 place.setBackgroundColor(objData.colors.back.off)
430 place.setTextColor(objData.colors.text.off)
431 end
432 for i = 0, objData.maxY-objData.minY do
433 place.setCursorPos(objData.minX,objData.minY+i)
434 place.write(string.rep(" ",objData.maxX-objData.minX+1))
435 end
436 if objData.text then
437 local xPos = objData.minX+math.ceil((objData.maxX-objData.minX+1)/2) - math.ceil(#tostring(objData.text)/2)
438 local yPos = objData.minY+(objData.maxY-objData.minY)/2
439 place.setCursorPos(xPos,yPos)
440 place.write(objData.text)
441 end
442end
443
444function toggleObjectState(name,screen)
445 screen = screen or curScreen
446 assert(screens[screen] ~= nil,"screen doesn't exist",2)
447 assert(screens[screen].objects[name] ~= nil,"Object '"..name.."' doesn't exsist",2)
448 if screens[screen].objects[name].state ~= true and screens[screen].objects[name].state ~= false then
449 screens[screen].objects[name].state = true
450 else
451 screens[screen].objects[name].state = not screens[screen].objects[name].state
452 end
453 return screens[screen].objects[name].state
454end
455
456function getObjectState(name,screen)
457 screen = screen or curScreen
458 assert(screens[screen] ~= nil,"screen doesn't exist",2)
459 assert(screens[screen].objects[name] ~= nil,"Object '"..name.."' doesn't exsist",2)
460 return screens[screen].objects[name].state
461end
462
463function changeObjectText(name,newText,screen)
464 screen = screen or curScreen
465 assert(screens[screen] ~= nil,"screen doesn't exist",2)
466 assert(screens[screen].objects[name] ~= nil,"Object '"..name.."' doesn't exsist",2)
467 assert(type(newText) == "nil" or type(newText) == "string","expected string,string or string,nil, got string"..type(newText))
468 screens[screen].objects[name].text = newText
469end
470
471function toggleObject(name,screen)
472 screen = screen or curScreen
473 assert(screens[screen] ~= nil,"screen doesn't exist",2)
474 assert(screens[screen].objects[name] ~= nil,"Object '"..name.."' doesn't exsist",2)
475 if type(screens[screen].objects[name].state) == "boolean" then
476 screens[screen].objects[name].state = "off"
477 else
478 screens[screen].objects[name].state = true
479 end
480 return screens[screen].objects[name].state
481end
482
483function setObjectText(name,setText,screen)
484 screen = screen or curScreen
485 assert(screens[screen] ~= nil,"screen doesn't exist",2)
486 assert(screens[screen].objects[name] ~= nil,"Object '"..name.."' doesn't exsist",2)
487 screens[screen].objects[name].text = setText
488end
489--End of Screen API
490
491local function getPath(name)
492 local toReturn = table.concat(curPath,"/")
493 if name then
494 toReturn = toReturn.."/"..name
495 elseif #curPath == 0 then
496 return ""
497 end
498 return toReturn
499end
500
501local function getCol(index,...)
502 local args = {...}
503 local col = settings.colors[colorType][index]
504 if type(col) == "table" then
505 col = col[args[1]]
506 end
507 if type(col) ~= "number" then
508 error("Color Scheme '"..colorType.."' doesn't have the color '"..index.."'",2)
509 end
510 return col
511end
512
513local function textColor(...)
514 term.setTextColor( getCol(...) )
515end
516
517local function backColor(...)
518 term.setBackgroundColor( getCol(...) )
519end
520
521local viewSizeCalc = {
522 Tiles = function()
523
524 end,
525 List = function()
526 maxY = h-1
527 maxX = w-1
528 perPage = maxY-minY
529 midPage = math.ceil(w/2)
530 pages = math.ceil(#dir/perPage)
531 if pages < 1 then pages = 1 end
532 page = 1
533 setScreen("screen_List")
534 for i = 1, perPage do
535 screens.screen_List.clickAreas[tostring(i)] = nil
536 addClickArea({
537 name = tostring(i),
538 minX = minX,
539 maxX = maxX,
540 minY = minY+i-1,
541 maxY = minY+i-1
542 })
543 end
544 end
545}
546
547local viewDrawScreen = {
548 Tiles = function()
549
550 end,
551 List = function()
552 for i = 1, perPage do
553 local curIndex = i+perPage*(page-1)
554 if curIndex > #dir then break end
555 local cur = dir[curIndex]
556 local back = getCol("back")
557 local text = getCol("text")
558 if cur.name == selected then back = getCol("selected") text = getCol("selectedText") end
559 term.setCursorPos(minX,minY+i-1)
560 term.setBackgroundColor(cur.back) term.write(" ") backColor("back") term.write(" ")
561 local toWrite = cur.name
562 if #toWrite > midPage-(minX+2) then
563 toWrite = toWrite:sub(1,midPage-(minX+2)-3).."..."
564 end
565 term.setBackgroundColor(back) term.setTextColor(text) term.write( toWrite..string.rep(" ",maxX-2-#toWrite) )
566 local size = getSize(getPath(cur.name),true)
567 if size ~= 0 then
568 term.setCursorPos(midPage+1,minY+i-1) term.write(size)
569 end
570 end
571 end
572}
573
574local function sortDir()
575 selected = nil numSelect = 0
576 dir = fs.list(getPath())
577 pathFolders,pathFiles = {},{}
578 for i = 1, #dir do
579 local fullPath = getPath(dir[i])
580 if fs.isDir( fullPath ) then
581 table.insert(pathFolders,dir[i])
582 else
583 table.insert(pathFiles,dir[i])
584 end
585 end
586 dir = {}
587 for i = 1, #pathFolders do table.insert(dir,{name = pathFolders[i],text = getCol("folderText"),back = getCol("folder"),type = "Folder"}) end
588 for i = 1, #pathFiles do table.insert(dir,{name = pathFiles[i],text = getCol("fileText"),back = getCol("file"),type = "File"}) end
589end
590
591local function drawScreen()
592 setScreen("main")
593 backColor("back")
594 term.clear()
595 paintutils.drawLine(1,1,w,1,getCol("headingBack"))
596 textColor("headingText")
597 printC( getPath() == "" and "Root - File Manager "..version or "/"..getPath() ,1)
598 backColor("back")
599 textColor("text")
600 printC("Page "..page.." of "..pages,h)
601 screens.main.objects.page_up.state = page > 1
602 drawObject("page_up")
603 screens.main.objects.page_down.state = page < pages
604 drawObject("page_down")
605 screens.main.objects.back.state = historyPos > 1
606 drawObject("back")
607 screens.main.objects.forward.state = historyPos < #pathHistory
608 drawObject("forward")
609 screens.main.objects.level_up.state = (getPath() ~= "")
610 drawObject("level_up")
611 viewDrawScreen[ curView ]()
612 textColor("text") backColor("back")
613 if showText then
614 printC(showText,h-1)
615 elseif selected then
616 printC(selected,h-1)
617 else
618 printC("Press 'h' for controls",h-1)
619 end
620end
621
622local function pageUp()
623 if page > 1 then page = page-1 drawScreen() end
624end
625
626local function pageDown()
627 if page < pages then page = page+1 drawScreen() end
628end
629
630local doSizeCalc = nil -- Thanks for this tip, Lignum :)
631
632local function back()
633 if historyPos > 1 then
634 historyPos = historyPos - 1
635 curPath = pathHistory[historyPos]
636 sortDir()
637 doSizeCalc()
638 drawScreen()
639 end
640end
641
642local function forward()
643 if historyPos < #pathHistory then
644 historyPos = historyPos + 1
645 curPath = pathHistory[historyPos]
646 sortDir()
647 doSizeCalc()
648 drawScreen()
649 end
650end
651
652local changePath = nil
653local function up()
654 if getPath() ~= "" then
655 local temp = {unpack(curPath)}
656 table.remove(temp,#temp)
657 changePath(temp)
658 selected = nil numSelect = 0
659 sortDir()
660 doSizeCalc()
661 drawScreen()
662 end
663end
664
665doSizeCalc = function()
666 w,h = term.getSize()
667 screens.main.objects = {}
668 setScreen("main")
669 addObject({
670 name = "page_down", text = "v",
671 minX = w, maxX = w,
672 minY = h-2, maxY = h-2,
673 colors = { back = { on = getCol("menuButtons","default"), off = getCol("menuButtons","disabled")}},
674 action = pageDown
675 })
676 addObject({
677 name = "page_up", text = "^",
678 minX = w, maxX = w,
679 minY = 3, maxY = 3,
680 colors = { back = { on = getCol("menuButtons","default"), off = getCol("menuButtons","disabled")}},
681 action = pageUp
682 })
683 addObject({
684 name = "back", text = "<",
685 minX = 3, maxX = 3,
686 minY = 2, maxY = 2,
687 colors = { back = { on = getCol("menuButtons","default"), off = getCol("menuButtons","disabled")}},
688 action = back
689 })
690 addObject({
691 name = "forward", text = ">",
692 minX = 5, maxX = 5,
693 minY = 2, maxY = 2,
694 colors = { back = { on = getCol("menuButtons","default"), off = getCol("menuButtons","disabled")}},
695 action = forward
696 })
697 addObject({
698 name = "level_up", text = "Go Up",
699 minX = 7, maxX = 11,
700 minY = 2, maxY = 2,
701 colors = { back = { on = getCol("menuButtons","default"), off = getCol("menuButtons","disabled")}},
702 action = up
703 })
704 viewSizeCalc[ curView ]()
705end
706
707changePath = function(newPath)
708 while historyPos < #pathHistory do
709 table.remove(pathHistory,#pathHistory)
710 end
711 curPath = newPath
712 if curPath[1] == "" then table.remove(curPath,1) end
713 table.insert(pathHistory,curPath)
714 historyPos = #pathHistory
715 sortDir()
716 showText = nil
717end
718
719local function openDir(name)
720 if fs.isDir(getPath(name)) then
721 selected = nil
722 numSelect = 0
723 local temp = {unpack(curPath)}
724 table.insert(temp,name)
725 changePath(temp)
726 doSizeCalc()
727 showText = nil
728 drawScreen()
729 return true
730 end
731 return false
732end
733
734local function enterDir()
735 backColor("headingBack") textColor("headingText")
736 local input = scrollRead(1,1,w,getPath())
737 if fs.isDir(input) then
738 changePath(split(input,"/"))
739 doSizeCalc()
740 else
741 showText = "Not a valid path"
742 end
743 drawScreen()
744end
745
746local function runProgram(path,...)
747 if multishell then
748 shell.run("fg",path,...)
749 else
750 term.setBackgroundColor(colors.black) term.setTextColor(colors.white) term.clear() term.setCursorPos(1,1)
751 shell.run(path,...)
752 term.setCursorBlink( false )
753 print( "Press enter to continue" )
754 repeat
755 local event,key = os.pullEvent("key")
756 until key == keys.enter
757 drawScreen()
758 end
759end
760
761local function properties()
762 local path,name
763 if selected then
764 path = getPath(selected)
765 name = selected
766 else
767 path = getPath()
768 name = fs.getName(path)
769 end
770
771 local function drawPropertiesScreen()
772 backColor("back") term.clear()
773 paintutils.drawLine(1,1,w,1,getCol("headingBack"))
774 textColor("headingText")
775 printC("Properties",1)
776 backColor("back") textColor("text")
777 term.setCursorPos(2,3) term.write("Name: "..name)
778 local writePath = path
779 if writePath == "" then
780 writePath = "/"
781 end
782 term.setCursorPos(2,4) term.write("Path: "..writePath)
783 term.setCursorPos(2,6) term.write("Type: ")
784 if fs.isDir(path) then
785 term.write("Folder")
786 else
787 term.write("File")
788 term.setCursorPos(2,7) term.write("Size: "..getSize(path))
789 end
790 local attributes = {}
791 if fs.isReadOnly(path) then table.insert(attributes,"Read-Only") end
792 if name == shell.getRunningProgram() then table.insert(attributes,"Running") end
793 if #attributes > 0 then
794 term.setCursorPos(2,8) term.write("Attributes: "..table.concat(attributes,","))
795 end
796 printC("Click anywhere or hit a key to close",h)
797 end
798
799 drawPropertiesScreen()
800 repeat
801 local event = os.pullEvent()
802 until event == "mouse_click" or event == "key"
803 doSizeCalc()
804 drawScreen()
805end
806
807local function window(heading,height,width)
808 width = width or #heading + 4
809 local windowMinY = math.ceil(h/2-height/2)
810 local minX = math.ceil(w/2-width/2)
811 if heading then
812 term.setCursorPos(minX,windowMinY) textColor("headingText") backColor("headingBack")
813 term.write(string.rep(" ",width)) printC(heading,windowMinY)
814 end
815 backColor("menuBack")
816 for i = 1, height do
817 term.setCursorPos(minX,windowMinY+i)
818 term.write(string.rep(" ",width))
819 end
820 return minX,minX+width,windowMinY
821end
822
823local function option(heading,choices,width,cancel)
824 if cancel then
825 table.insert(choices,{text = "Cancel",color = getCol("menuButtons","cancel")})
826 end
827 local _,_,windowMinY = window(heading,(#choices*2)+1,width)
828 if screens[name] then screens[name] = nil end
829 addScreen("option",colors.white,true)
830 local largest = 0
831 for i = 1, #choices do
832 if #choices[i].text > largest then
833 largest = #choices[i].text
834 end
835 end
836 largest = largest + 2
837 local pos = 2
838 local minButtonX = math.floor(w/2 - largest/2)
839 for i = 1, #choices do
840 addObject({
841 text = choices[i].text,
842 name = tostring(i),
843 minX = minButtonX,
844 maxX = minButtonX+largest,
845 minY = windowMinY+pos,
846 maxY = windowMinY+pos,
847 colors = {
848 text = { on = getCol("menuButtons","textSelected"), off = getCol("menuButtons","text")},
849 back = {
850 on = getCol("selected"),
851 off = (choices[i].color or getCol("menuButtons","default"))
852 }
853 }
854 })
855 toggleObjectState(tostring(i))
856 drawObject(tostring(i))
857 pos = pos+2
858 end
859 toggleObjectState("1")
860 drawObject("1")
861 local selectButton = 1
862 while true do
863 local event = {os.pullEvent()}
864 if event[1] == "mouse_click" then
865 local _,name = checkPos(event[3],event[4])
866 if name then selectButton = tonumber(name) break end
867 elseif event[1] == "key" then
868 if event[2] == keys.down then
869 if selectButton < #choices then
870 toggleObjectState(tostring(selectButton))
871 drawObject(tostring(selectButton))
872 selectButton = selectButton+1
873 toggleObjectState(tostring(selectButton))
874 drawObject(tostring(selectButton))
875 end
876 elseif event[2] == keys.up then
877 if selectButton > 1 then
878 toggleObjectState(tostring(selectButton))
879 drawObject(tostring(selectButton))
880 selectButton = selectButton-1
881 toggleObjectState(tostring(selectButton))
882 drawObject(tostring(selectButton))
883 end
884 elseif event[2] == keys.enter then
885 break
886 end
887 end
888 end
889 screens.option = nil
890 doSizeCalc()
891 drawScreen()
892 return selectButton
893end
894
895local function getInput(heading,insertText,requireText)
896 os.queueEvent("Distraction") os.pullEvent("Distraction")
897 heading = heading or "Enter Text"
898 local windowMinX,windowMaxX,windowMinY = window(heading,3)
899 backColor("inputBack") textColor("inputText")
900 local input
901 repeat
902 input = scrollRead(windowMinX+1,windowMinY+2,windowMaxX-windowMinX-2,insertText)
903 until input ~= "" or not requireText
904 doSizeCalc()
905 drawScreen()
906 return input
907end
908
909local function displayList(heading,elements)
910 local listTop = 3
911 local listBottom = h-2
912 local listLeft = 2
913 local perPage = listBottom-listTop
914 local tLines = {}
915
916 for e = 1, #elements do
917 if e ~= 1 then table.insert(tLines," ") end
918 table.insert(tLines,elements[e][1])
919 for a = 1, #elements[e][2] do
920 table.insert(tLines," "..elements[e][2][a])
921 end
922 end
923
924 pages = math.ceil(#tLines/perPage)
925
926 local function redrawList()
927 backColor("back") term.clear()
928 paintutils.drawLine(1,1,w,1,getCol("headingBack"))
929 textColor("headingText") printC(heading,1)
930 for i = 1, #tLines do
931 if i > perPage then break end
932 local cur = i+perPage*(page-1)
933 if cur > #tLines then break end
934 textColor("text") backColor("back")
935 term.setCursorPos(listLeft,listTop+i-1)
936 term.write(tLines[cur])
937 end
938 printC("Hit 'enter' to continue",h)
939 end
940
941 redrawList()
942 while true do
943 local event = {os.pullEvent()}
944 if event[1] == "key" then
945 if event[2] == keys.enter then
946 if page < pages then
947 page = page + 1
948 redrawList()
949 else
950 doSizeCalc()
951 drawScreen()
952 return
953 end
954 end
955 end
956 end
957end
958
959local function runProgArgs(path)
960 local args = split(getInput("Enter Args - Separate each with a space",nil,true))
961 runProgram(path,unpack(args))
962end
963
964local function rename()
965 if not fs.isReadOnly(getPath(selected)) then
966 local input = getInput(" Rename Item ",selected,true)
967 if fs.exists(getPath(input)) then
968 showText = "Already exists!"
969 else
970 fs.move( getPath(selected) , getPath(input) )
971 selected = nil
972 numSelect = 0
973 sortDir()
974 end
975 else
976 showText = "This item is read-only"
977 end
978 drawScreen()
979end
980
981local function create()
982 if not fs.isReadOnly(getPath()) then
983 local options = {}
984 for i = 1, #items do
985 options[i] = {text = items[i].name}
986 end
987 local choice = option("Create...",options,nil,true)
988 if choice ~= #options then
989 local input = getInput(" Enter ".. items[choice].name .." Name ",nil,true)
990 local itemPath = getPath(input)
991 if not fs.exists(itemPath) then
992 if not fs.isReadOnly(getPath()) then
993 if not items[choice].create( itemPath ) then
994 showText = "Unable to create "..items[choice].name
995 end
996 else
997 end
998 else
999 showText = input.." already exists"
1000 end
1001 sortDir()
1002 end
1003 else
1004 showText = "This folder is read-only"
1005 end
1006 drawScreen()
1007end
1008
1009local function delete()
1010 if not fs.isReadOnly(getPath(selected)) then
1011 if option("Delete selected item?",{{text = "Delete"},{text = "Cancel"}}) == 1 then
1012 fs.delete(getPath(selected))
1013 sortDir()
1014 end
1015 else
1016 showText = "This item is read-only"
1017 end
1018 drawScreen()
1019end
1020
1021local function selectView()
1022 local temp = {}
1023 for i = 1, #displayViews do
1024 table.insert(temp,{text = displayViews[i]})
1025 end
1026 local choice = option("Select View (Current:"..curView..")",temp)
1027 screens["screen_"..curView] = nil
1028 curView = displayViews[choice]
1029 doSizeCalc()
1030 drawScreen()
1031end
1032
1033local function help()
1034 displayList("File Manager Help",{
1035 {"General",
1036 {
1037 "Click / Use arrows to select an item",
1038 "Click / Enter to open selected item",
1039 "< and > / Forward and Back to navigate history",
1040 "Go Up / 'u' to go up a level",
1041 "Ctrl + (Click / Enter) to run with arguments"
1042 }
1043 },
1044 {"Mouse Controls",
1045 {
1046 "Left click to select an item",
1047 "Left click selected item to open or run",
1048 "Right Click background for options",
1049 "Right Click an item for options",
1050 "Click 'v' / '^' or scroll to navagate pages"
1051 }
1052 },
1053 {"Keyboard Controls",
1054 {
1055 "'x' to quit",
1056 "'o' for selected item options",
1057 "'o' with nothing selected for more options",
1058 "'c' to create an item",
1059 "'Tab' to enter a path",
1060 "Page Up / Down to navagate pages",
1061 "'e' to edit current item (Files only)",
1062 "'p' for selected item's Properties",
1063 "'r' to rename selected item",
1064 "'d' to delete selected item",
1065 "'u' to go up a level",
1066 "'Enter' to run or open",
1067 "Left / Right Arrow to navagate history",
1068 }
1069 }
1070 })
1071end
1072
1073local function itemOptions()
1074 local temp = {
1075 {text = "Run"},
1076 {text = "Run with args"},
1077 {text = "Edit"},
1078 {text = "Rename"},
1079 {text = "Delete"},
1080 {text = "New..."},
1081 {text = "Properties"}
1082 }
1083 local choice = option(nil,temp,22,true)
1084 if choice == 1 then
1085 runProgram(selected)
1086 elseif choice == 2 then
1087 runProgArgs(getPath(selected))
1088 elseif choice == 3 then
1089 runProgram("edit",selected)
1090 elseif choice == 4 then
1091 rename()
1092 elseif choice == 5 then
1093 delete()
1094 elseif choice == 6 then
1095 create()
1096 elseif choice == 7 then
1097 properties()
1098 end
1099end
1100
1101local function dirOptions()
1102 local temp = {
1103 {text = "View..."},
1104 {text = "New..."},
1105 {text = "Controls"},
1106 {text = "Properties"},
1107 {text = "Quit File Manager"}
1108 }
1109 local choice = option(nil,temp,22,true)
1110 if choice == 1 then
1111 selectView()
1112 elseif choice == 2 then
1113 create()
1114 elseif choice == 3 then
1115 help()
1116 elseif choice == 4 then
1117 properties()
1118 elseif choice == 5 then
1119 error("Terminated")
1120 end
1121end
1122
1123
1124--=== Program ===--
1125--Determine which color scheme to use
1126if term.isColor() then
1127 colorType = "Full Color"
1128elseif _CC_VERSION or _HOST then
1129 colorType = "Gray Scale"
1130else -- Must be using a CC version before Gray Scale
1131 print("Black and White displays are not supported at this time")
1132 print("You'll need to use and Advanced (Gold) Computer")
1133 return
1134 --colorType = "Black and White"
1135end
1136
1137--Main Loop
1138local function main()
1139 addScreen("main",colors.white,true)
1140 addScreen("screen_List",colors.white)
1141 sortDir()
1142 doSizeCalc()
1143 drawScreen()
1144 while true do
1145 local event = { os.pullEvent() }
1146
1147 if event[1] == "mouse_click" then
1148 if event[4] == 1 then
1149 enterDir()
1150 elseif not checkPos(event[3],event[4],"main") then
1151 local element,name = checkPos(event[3],event[4],"screen_List")
1152 if name then
1153 local cur = tonumber(name)+perPage*(page-1)
1154 if cur <= #dir then
1155 if event[2] == 1 then
1156 if cur == numSelect then
1157 if not openDir(selected) then
1158 if ctrlHeld then
1159 runProgArgs(getPath(selected))
1160 else
1161 runProgram(getPath(selected))
1162 end
1163 end
1164 else
1165 selected = dir[cur].name
1166 numSelect = cur
1167 drawScreen()
1168 end
1169 elseif event[2] == 2 then
1170 selected = dir[cur].name
1171 numSelect = cur
1172 drawScreen()
1173 itemOptions()
1174 end
1175 else
1176 selected = nil
1177 numSelect = nil
1178 drawScreen()
1179 if event[2] == 2 then dirOptions() end
1180 end
1181 else
1182 selected = nil
1183 numSelect = nil
1184 drawScreen()
1185 end
1186 end
1187 elseif event[1] == "key" then
1188 if event[2] == keys.down then
1189 if not selected and #dir ~= 0 then
1190 numSelect = 1
1191 selected = dir[numSelect].name
1192 page = 1
1193 drawScreen()
1194 elseif numSelect < #dir then
1195 numSelect = numSelect+1
1196 selected = dir[numSelect].name
1197 local selectedPage = math.ceil(numSelect/perPage)
1198 if selectedPage ~= page then page = selectedPage end
1199 drawScreen()
1200 end
1201 elseif event[2] == keys.up then
1202 if not selected and #dir ~= 0 then
1203 numSelect = 1
1204 selected = dir[numSelect].name
1205 drawScreen()
1206 elseif numSelect > 1 then
1207 numSelect = numSelect-1
1208 selected = dir[numSelect].name
1209 local selectedPage = math.ceil(numSelect/perPage)
1210 if selectedPage ~= page then page = selectedPage end
1211 drawScreen()
1212 end
1213 elseif event[2] == keys.enter then
1214 if selected then
1215 if not openDir(selected) then
1216 if ctrlHeld then
1217 runProgArgs(getPath(selected))
1218 else
1219 runProgram(getPath(selected))
1220 end
1221 end
1222 end
1223 elseif event[2] == keys.leftShift then
1224 shiftHeld = true
1225 elseif event[2] == keys.leftCtrl then
1226 ctrlHeld = true
1227 elseif event[2] == keys.pageUp then
1228 pageUp()
1229 elseif event[2] == keys.pageDown then
1230 pageDown()
1231 elseif event[2] == keys.tab then
1232 enterDir()
1233 elseif event[2] == keys.x then
1234 error("Terminated")
1235 elseif event[2] == keys.e then
1236 os.pullEvent("char") -- Get that stray 'e' char event
1237 runProgram("/rom/programs/edit",getPath(selected))
1238 elseif event[2] == keys.p then
1239 properties()
1240 elseif event[2] == keys.r and selected then
1241 rename()
1242 elseif event[2] == keys.c then
1243 create()
1244 elseif event[2] == keys.d and selected then
1245 delete()
1246 elseif event[2] == keys.u then
1247 up()
1248 elseif event[2] == keys.left then
1249 back()
1250 elseif event[2] == keys.right then
1251 forward()
1252 elseif event[2] == keys.h then
1253 help()
1254 elseif event[2] == keys.o then
1255 if selected then
1256 itemOptions()
1257 else
1258 dirOptions()
1259 end
1260 end
1261 elseif event[1] == "key_up" then
1262 if event[2] == keys.leftShift then
1263 shiftHeld = false
1264 elseif event[2] == keys.leftCtrl then
1265 ctrlHeld = false
1266 end
1267 elseif event[1] == "term_resize" then
1268 doSizeCalc()
1269 drawScreen()
1270 elseif event[1] == "mouse_scroll" then
1271 if event[2] > 0 then
1272 pageDown()
1273 else
1274 pageUp()
1275 end
1276 end
1277 end
1278end
1279
1280local state,err = pcall(main)
1281if not state then
1282 if err then
1283 if err:find("Terminated") then
1284 term.setBackgroundColor(colors.black) term.setTextColor(colors.white)
1285 term.clear()
1286 printC("Thanks for using File Manager "..version,1)
1287 printC("By HPWebcamAble",2)
1288 term.setCursorPos(1,3)
1289 elseif err:find("FORCEQUIT") then
1290 -- Do nothing
1291 else
1292 term.setBackgroundColor(colors.black)
1293 term.setTextColor(colors.white)
1294 term.write("X")
1295 paintutils.drawLine(1,1,w,1,colors.black)
1296 printC("Error - Press Enter",1)
1297 repeat
1298 local event,key = os.pullEvent("key")
1299 until key == keys.enter
1300 term.clear()
1301 printC("Error!",1)
1302 term.setCursorPos(1,2)
1303 print(err)
1304 print(" ")
1305 end
1306 else
1307 term.setBackgroundColor(colors.black) term.setTextColor(colors.white)
1308 term.clear()
1309 printC("An known error occured",1)
1310 print(" ")
1311 end
1312end
1313
1314os.queueEvent("Distraction")
1315os.pullEvent("Distraction") -- Clear the event queue