· 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