Advertisement
Encreedem

Graffiti v1.6.2

Oct 28th, 2013
193
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 95.49 KB | None | 0 0
  1. -- Author: Encreedem
  2. -- Link: http://www.computercraft.info/forums2/index.php?/topic/13944-161-graffiti-the-first-ide-for-guis/
  3.  
  4. -- Notes to myself:
  5. -- TODO: The Progress Bars movePos and scalePos get messed up when it's facing up.
  6. -- TODO: Replace "get...Value()" with "objects.ObjectType.load()"
  7.  
  8. local version = "1.6.2"
  9.  
  10. -- >>> Variables
  11.  
  12. -- Tables for users:
  13. local userFunctions = {}
  14. local userLists = {}
  15. local userInputs = {}
  16. local selectedItems = {}
  17. local selectedFiles = {}
  18. local toggleState = {}
  19.  
  20. --Monitor
  21. local monitor = nil
  22.  
  23. -- Text
  24. local text = {}
  25. text.back = " < "
  26. text.quit = " X "
  27. text.refresh = "Refresh"
  28. text.done = "Done"
  29. text.options = "Options"
  30. --text.fileSelector = "[...]"
  31.  
  32. -- Colors
  33. local objectColors = {}
  34.  
  35. -- Color Themes
  36. local colorThemes = {}
  37. colorThemes.Default = {
  38.   background = colors.black;
  39.   text = colors.white;
  40.   Button = { default = colors.red; active = colors.lime; text = colors.white };
  41.   ProgressBar = { low = colors.red; medium = colors.yellow; high = colors.lime; background = colors.gray; };
  42.   Input = { default = colors.white; text = colors.black; active = colors.yellow; };
  43.   List = { default = colors.blue; active = colors.lightBlue; };
  44.   FileSelector = { default = colors.red; text = colors.white; dir = colors.lime; file = colors.white; active = colors.lime; };
  45.   Editor = {
  46.     new = colors.white;
  47.     active = colors.lime;
  48.     move = colors.magenta;
  49.     scale = colors.pink;
  50.     marker = colors.gray;
  51.     editMarker = colors.lime;
  52.     alignmentTrue = colors.lime;
  53.     alignmentFalse = colors.red;
  54.   };
  55.   Container = {
  56.     Panel = {
  57.       border = colors.white;
  58.     };
  59.     ScrollView = {
  60.       border = colors.white;
  61.       scrollBackground = colors.lightGray;
  62.       scrollForeground = colors.gray;
  63.     };
  64.   };
  65. }
  66.  
  67. colorThemes["Windows CC"] = {
  68.   background = colors.white;
  69.   text = colors.black;
  70.   Button = { default = colors.lightGray; active = colors.lightBlue; text = colors.black };
  71.   ProgressBar = { low = colors.lime; medium = colors.lime; high = colors.lime; background = colors.lightGray; };
  72.   Input = { default = colors.lightGray; text = colors.black; active = colors.white; };
  73.   List = { default = colors.lightGray; active = colors.lightBlue; };
  74.   FileSelector = { default = colors.gray; text = colors.black; dir = colors.blue; file = colors.black; active = colors.lightBlue; };
  75.   Editor = {
  76.     new = colors.lightBlue;
  77.     active = colors.black;
  78.     move = colors.magenta;
  79.     scale = colors.pink;
  80.     marker = colors.gray;
  81.     editMarker = colors.lightBlue;
  82.     alignmentTrue = colors.lime;
  83.     alignmentFalse = colors.red;
  84.   };
  85.   Container = {
  86.     Panel = {
  87.       border = colors.lightGray;
  88.     };
  89.     ScrollView = {
  90.       border = colors.lightGray;
  91.       scrollBackground = colors.lightGray;
  92.       scrollForeground = colors.black;
  93.     };
  94.   };
  95. }
  96.  
  97. objectColors = colorThemes.Default
  98.  
  99. -- Sizes
  100. local size = {}
  101. size.Button = { width = 10; height = 3; }
  102. size.ProgressBar = { length = 10; }
  103. size.Container = { width = 20; height = 10; }
  104.  
  105. -- Files Info
  106. local root = "/" -- The path where the data-folder will be created by default.
  107. local dataFolderPath = nil
  108. local loadedFiles = {}
  109. local currentProject = "Default"
  110.  
  111. -- API
  112. local initDone = false
  113. local isAPI = false -- Determines whether the program has been loaded as an API
  114. local variableValues = {}
  115. local progressBarValues = {}
  116.  
  117. -- Editor Options
  118. local editMode = false
  119. local showEditorOptions = false
  120. local saveAfterQuit = true
  121. local editActions = { "Design", "Attributes", "Delete" }
  122. local lastWindow = "mainWindow"
  123. local editorFunctions = {}
  124. local rightClickActions = {"Attributes", "Delete" }
  125.  
  126. -- tables
  127. local args = { ... }
  128. local sides = { "left", "top", "right", "bottom", "front", "back" }
  129. local objectTypes = { "Button", "Text", "Variable", "ProgressBar", "Input", "List", "Panel", "ScrollView" }
  130. local eventTypes = { "quit", "button_clicked", "button_toggled", "selection_changed", "text_changed" }
  131. local objects = {}
  132. local windows = {children = { mainWindow = { objType = "Window", children = {} } }}
  133. local windowBuffer = nil
  134. local defaultButtons = {}
  135.  
  136. -- Other
  137. local quit = false
  138. local doLog = true -- Determines wheter a log file should be created or not.
  139. local maxX, maxY = 51, 19
  140. local out = term -- Output: either "term" or the monitor
  141. local outIsTerm = false
  142. local autoLoadObjects = true
  143. local changeButtonColor = true
  144. local currentWindow = "mainWindow"
  145.  
  146. -- Settings
  147. local settings = {}
  148. settings.root = root
  149. settings.startupProject = currentProject
  150. settings.language = "en-US"
  151. settings.colorTheme = "Default"
  152.  
  153. -- >> User Code: (This section is just for YOU! :D)
  154.  
  155. -- user variables
  156. local randomValue= 50
  157.  
  158. -- user functions
  159.  
  160. --[[ How to make your own functions:
  161. Note: toggleState is only needed if the buttons'
  162. funcType-attribute is set to "toggle function"
  163.  
  164. function userFunctions.<paramAttribute>(toggleState)
  165.   your code here
  166. end
  167. ]]
  168.  
  169. function userFunctions.test()
  170.   sleep(1)
  171. end
  172.  
  173. function userFunctions.setRandomValue()
  174.   randomValue = math.random(100)
  175. end
  176.  
  177. function userFunctions.refresh()
  178.   drawWindow()
  179. end
  180.  
  181. function userFunctions.toggleTest(toggleState)
  182.   rs.setOutput("front", toggleState)
  183. end
  184.  
  185. -- user lists
  186.  
  187. userLists.testList = {
  188.   "Testitem 1",
  189.   "Testitem 2",
  190.   "Testitem 3"
  191. }
  192.  
  193. -- Define the value of a variable-object.
  194. function getVariableValue(object)
  195.   assert(object)
  196.   local objID = object.objID
  197.  
  198.   if (objID == "testVariable") then
  199.     return "Variable";
  200.   elseif (objID == "Time") then
  201.     return textutils.formatTime(os.time(), true)
  202.   else
  203.     return variableValues[objID]
  204.   end
  205. end
  206.  
  207. -- Definie the value of a progressBar-object
  208. -- 0: empty; 100: full
  209. function getProgressBarValue(object)
  210.   assert(object)
  211.   local objID = object.objID
  212.  
  213.   if (objID == "testProgressBar") then
  214.     return 87
  215.   elseif (objID == "randomProgressBar") then
  216.     return randomValue
  217.   else
  218.     return progressBarValues[objID]
  219.   end
  220. end
  221.  
  222. --[[ WARNING!
  223. Everything below this comment
  224. shouldn't be edited!
  225. If you do so and the program doesn't work anymore
  226. then it's your fault!
  227. ]]
  228.  
  229. --[[ Displays the text, the content of a table
  230. or a star in the upper left corner until you press
  231. a key.]]
  232. function dBug(text)
  233.   out.setCursorPos(1, 1)
  234.  
  235.   if (text == nil) then
  236.     out.write("*")
  237.     getKeyInput()
  238.     out.setCursorPos(1, 1)
  239.     out.write(" ")
  240.     return
  241.   elseif (type(text) == "table") then
  242.     for key, value in pairs(text) do
  243.       print(key .. ": " .. tostring(value))
  244.     end
  245.   else
  246.     out.write(text)
  247.   end
  248.  
  249.   getKeyInput()
  250. end
  251.  
  252. -- Checks whether the table contains the given content-parameter.
  253. function tableContains(tbl, content)
  254.   if (tbl and type(tbl) == "table") then
  255.     for _, value in pairs(tbl) do
  256.       if (value == content) then
  257.         return true
  258.       end
  259.     end
  260.    
  261.     return false
  262.   else
  263.     return false
  264.   end
  265. end
  266.  
  267. -- >> Files
  268. local Files = {}
  269.  
  270. -- Returns whether the filename ends with the
  271. -- given extension.
  272. function Files.endsWith(filename, extension)
  273.   assert(filename)
  274.   assert(extension)
  275.  
  276.   local sStart, sEnd = string.find(filename, extension, (#extension * -1))
  277.   return (sStart ~= nil)
  278. end
  279.  
  280. -- Serializes a normal or serialized table into a
  281. -- readable format which can be saved in a file.
  282. function Files.serialize(toSave)
  283.   local saveText
  284.   local serialized = ""
  285.   local indentation = 0
  286.   local indent = false
  287.  
  288.   if (type(toSave) == "string") then
  289.     saveText = toSave
  290.   elseif (type(toSave) == "table") then
  291.     saveText = textutils.serialize(toSave)
  292.   else
  293.     error("Can't save variable of type .. " .. type(toSave) .. "!", 1)
  294.   end
  295.  
  296.   for char in saveText:gmatch(".") do
  297.     if (char == "{") then
  298.       indentation = indentation + 1
  299.      
  300.       serialized = serialized .. char
  301.       serialized = serialized .. "\n"
  302.       indent = true
  303.     elseif (char == ",") then
  304.       serialized = serialized .. char .. "\n"
  305.       indent = true
  306.     elseif (char == "}") then
  307.       indentation = indentation - 1
  308.       serialized = serialized .. string.rep("\t", indentation)
  309.       serialized = serialized .. char
  310.       indent = true
  311.     else
  312.       if indent then
  313.         serialized = serialized .. string.rep("\t", indentation)
  314.         indent = false
  315.       end
  316.      
  317.       serialized = serialized .. char
  318.     end
  319.   end
  320.  
  321.   serialized = serialized:gsub(" ", "<SPACE>")
  322.   return serialized
  323. end
  324.  
  325. -- Unserializes a string that has been serialized
  326. -- using the "Files.serialize" function into a
  327. -- table.
  328. function Files.unserialize(str)
  329.   local saveText = str
  330.   saveText = saveText:gsub("\n", "")
  331.   saveText = saveText:gsub("\t", "")
  332.   saveText = saveText:gsub(" ", "")
  333.   saveText = saveText:gsub("<SPACE>", " ")
  334.   saveText = textutils.unserialize(str)
  335.  
  336.   return saveText
  337. end
  338.  
  339. -- Initializes the Graffiti data folder.
  340. function Files.init()
  341.   if (fs.exists(fs.combine(root, ".GraffitiData"))) then
  342.     dataFolderPath = fs.combine(root, ".GraffitiData")
  343.   elseif (fs.exists(fs.combine(root, "GraffitiData"))) then
  344.     dataFolderPath = fs.combine(root, "GraffitiData")
  345.   else
  346.     runSetup()
  347.   end
  348.  
  349.   for key, value in pairs(Files) do
  350.     if (type(value) == "table") then -- If it's an actual file type instead of a function.
  351.       if (value.autoLoad and value.load) then
  352.         Files.load(key)
  353.       end
  354.     end
  355.   end
  356. end
  357.  
  358. -- Saves all files with
  359. function Files.save()
  360.   for _, file in pairs(Files) do
  361.     if (type(file) == "table") then -- If it's an actual file type instead of a function.
  362.       if (file.autoSave and file.save) then
  363.         file.save()
  364.       end
  365.     end
  366.   end
  367. end
  368.  
  369. -- Loads the file and all of its required files.
  370. function Files.load(fileType)
  371.   if not tableContains(loadedFiles, fileType) then
  372.     local loadRequired = Files[fileType].loadRequired
  373.    
  374.     if (loadRequired) then
  375.       for _, value in pairs(loadRequired) do
  376.         Files.load(value)
  377.       end
  378.     end
  379.    
  380.     Files[fileType].load()
  381.     table.insert(loadedFiles, fileType)
  382.   end
  383. end
  384.  
  385. function Files.saveTable(subDir, fileName, tbl)
  386.   local fileHandle = Files.createDataFile(subDir, fileName, "w")
  387.  
  388.   for key, value in pairs(tbl) do
  389.     fileHandle.writeLine(key .. "=" .. value)
  390.   end
  391.  
  392.   if fileHandle then
  393.     fileHandle.close()
  394.   end
  395. end
  396.  
  397. function Files.loadTable(subDir, fileName)
  398.   local fileHandle = Files.getDataFileHandle(subDir, fileName, "r", false)
  399.   local ret = {}
  400.  
  401.   if not fileHandle then
  402.     return nil
  403.   end
  404.  
  405.   repeat
  406.     local line = fileHandle.readLine()
  407.    
  408.     if (line ~= nil and line ~= "" and string.find(line, "=")) then
  409.       local key, value = splitAt(line, "=")
  410.       ret[key] = value
  411.     end
  412.   until line == nil
  413.  
  414.   if fileHandle then
  415.     fileHandle.close()
  416.   end
  417.  
  418.   return ret
  419. end
  420.  
  421. -- Removes the content of a file.
  422. function Files.clear(subDir, fileName)
  423.   local fileHandle = Files.getDataFileHandle(subDir, fileName, "w", false)
  424.  
  425.   if fileHandle then
  426.     fileHandle.close()
  427.   end
  428. end
  429.  
  430. -- Returns the handle of an existing file or null.
  431. -- The file will automatically be created if
  432. -- autoCreate is true.
  433. function Files.getDataFileHandle(subDir, filename, mode, autoCreate)
  434.   assert(filename)
  435.   assert(mode)
  436.  
  437.   local ret
  438.   local path
  439.  
  440.   -- Create the path to the file if it doesn't exist.
  441.   if subDir then
  442.     local subDirPath
  443.    
  444.     if (type(subDir) == "string" and subDir ~= "") then
  445.       subDirPath = fs.combine(dataFolderPath, subDir)
  446.       path = fs.combine(subDirPath, filename)
  447.      
  448.       if (fs.exists(subDirPath)) then
  449.         if (not fs.isDir(subDirPath)) then
  450.           error("Can't create directory " .. subDir .. "! A file with that name exists!", 0)
  451.         end
  452.       else
  453.         fs.makeDir(subDirPath)
  454.       end
  455.     elseif (type(subDir) == "table") then
  456.       local subDirPath = dataFolderPath
  457.      
  458.       for _, dir in pairs(subDir) do
  459.         subDirPath = fs.combine(subDirPath, dir)
  460.      
  461.         if (fs.exists(subDirPath)) then
  462.           if (not fs.isDir(subDirPath)) then
  463.             error("Can't create directory " .. subDir .. "! A file with that name exists!", 0)
  464.           end
  465.         else
  466.           fs.makeDir(subDirPath)
  467.         end
  468.       end
  469.      
  470.       path = fs.combine(subDirPath, filename)
  471.     end
  472.   else
  473.     path = fs.combine(dataFolderPath, filename)
  474.   end
  475.  
  476.   if (fs.exists(path) and fs.isReadOnly(path)) then
  477.     error("File " .. filePath .. " is readonly!", 0)
  478.   end
  479.  
  480.   if (fs.exists(path) or mode == "w") then
  481.     ret = fs.open(path, mode)
  482.   elseif (autoCreate) then
  483.     ret = fs.open(path, "w") or error("Can't open file " .. path, 2)
  484.   end
  485.  
  486.   return ret
  487. end
  488.  
  489. -- Creates or overrides a file and returns its handle.
  490. function Files.createDataFile(subDir, filename)
  491.   return Files.getDataFileHandle(subDir, filename, "w")
  492. end
  493.  
  494. -- >> Save File
  495. Files.Save = {}
  496.  
  497. Files.Save.autoSave = true
  498. Files.Save.autoLoad = true
  499. Files.Save.loadRequired = { "Settings" }
  500. Files.Save.extension = ".window"
  501.  
  502. -- Saves the content of the windows-table into the
  503. -- save file.
  504. function Files.Save.save()
  505.   for name, window in pairs(windows.children) do
  506.     if (type(window) == "table") then
  507.       local saveString = Files.serialize(window)
  508.       local fileHandle = Files.createDataFile({ "Save", currentProject }, name .. Files.Save.extension )
  509.      
  510.       fileHandle.write(saveString)
  511.       fileHandle.close()
  512.     end
  513.   end
  514. end
  515.  
  516. -- Loads the save file and puts the content into
  517. -- the windows-table
  518. function Files.Save.load()
  519.   local path = fs.combine(dataFolderPath, "Save")
  520.   path = fs.combine(path, currentProject)
  521.  
  522.   if not path or not fs.isDir(path) then
  523.     return
  524.   end
  525.  
  526.   for _, filename in pairs(fs.list(path)) do
  527.     local filePath = fs.combine(path, filename)
  528.     local windowName = string.sub(filename, 1, #filename - #Files.Save.extension)
  529.    
  530.     if (not fs.isDir(filePath) and Files.endsWith(filename, Files.Save.extension)) then
  531.       local fileHandle = Files.getDataFileHandle({ "Save", currentProject }, filename, "r", false)
  532.      
  533.       if fileHandle then
  534.         local loadString = fileHandle.readAll()
  535.         loadString = Files.unserialize(loadString)
  536.        
  537.         if (loadString ~= nil and loadString ~= "") then
  538.           windows.children[windowName] = loadString
  539.         end
  540.        
  541.         fileHandle.close()
  542.       end
  543.     end
  544.   end
  545. end
  546.  
  547. -- >> Settings File
  548. Files.Settings = {}
  549. Files.Settings.autoSave = true
  550. Files.Settings.autoLoad = true
  551. Files.Settings.subDir = nil
  552. Files.Settings.name = "Graffiti.cfg"
  553.  
  554. function Files.Settings.init()
  555.   if not settings then
  556.     return
  557.   end
  558.  
  559.   for key, value in pairs(settings) do
  560.     if (key == "startupProject") then
  561.       currentProject = value
  562.     elseif (key == "language") then
  563.       settings.language = value
  564.     elseif (key == "colorTheme" and colorThemes[value]) then
  565.       objectColors = colorThemes[value]
  566.     end
  567.   end
  568. end
  569.  
  570. -- Creates the settings file in the data-folder and fills it with default values.
  571. function Files.Settings.save()
  572.   Files.saveTable(Files.Settings.subDir, Files.Settings.name, settings)
  573. end
  574.  
  575. -- Loads the settings file.
  576. function Files.Settings.load()
  577.   local tbl = Files.loadTable(Files.Settings.subDir, Files.Settings.name)
  578.  
  579.   if tbl then
  580.     for key, value in pairs(tbl) do
  581.       settings[key] = value
  582.     end
  583.   end
  584.  
  585.   Files.Settings.init()
  586. end
  587.  
  588. -- >> Language File
  589.  
  590. Files.Language = {
  591.   autoSave = true;
  592.   autoLoad = true;
  593.   subDir = "Language";
  594.   extension = ".lang";
  595.   loadRequired = { "Settings" };
  596. }
  597.  
  598. function Files.Language.save()
  599.   local fileName = "Graffiti." .. settings.language .. Files.Language.extension
  600.   Files.saveTable(Files.Language.subDir, fileName, text)
  601. end
  602.  
  603. function Files.Language.load()
  604.   local fileName = "Graffiti." .. settings.language .. Files.Language.extension
  605.   local tbl = Files.loadTable(Files.Language.subDir, fileName)
  606.  
  607.   if tbl then
  608.     for key, value in pairs(tbl) do
  609.       text[key] = value
  610.     end
  611.   end
  612. end
  613.  
  614. -- >> Log File
  615. Files.Log = {
  616.   autoSave = false;
  617.   autoLoad = false;
  618.   subDir = "Log";
  619.   name = "Graffiti.log";
  620. }
  621.  
  622. -- Writes the text into a logfile.
  623. function log(text, logType)
  624.   if not doLog then
  625.     return
  626.   end
  627.  
  628.   local logFileHandle = Files.getDataFileHandle(Files.Log.subDir, Files.Log.name, "a", true)
  629.  
  630.   if (type(text) == "table") then
  631.     local delimiter = ""
  632.    
  633.     logFileHandle.write((logType or "INFO") .. ": ")
  634.    
  635.     for key, value in pairs(text) do
  636.       logFileHandle.write(delimiter .. key .. "=" .. tostring(value))
  637.       delimiter = ", "
  638.     end
  639.    
  640.     logFileHandle.writeLine()
  641.   else
  642.     logFileHandle.writeLine((logType or "INFO") .. ": " .. tostring(text))
  643.   end
  644.  
  645.   logFileHandle.close()
  646. end
  647.  
  648. -- >>> Shortcut functions (for key inputs)
  649. -- Not yet implemented! WIP!
  650. -- TODO: Fix bug where the key event won't disappear from the queue.
  651.  
  652. function callShortcut(key)
  653.   --if (key and shortcut[key]) then
  654.   --  shortcut[key]()
  655.   --end
  656. end
  657.  
  658. shortcut = {}
  659.  
  660. -- S
  661. shortcut[31] = function()
  662.   out.clear()
  663.   out.setCursorPos(1, 1)
  664.   print("Saving windows...")
  665.  
  666.   Files.Save.save()
  667.  
  668.   print("Windows saved!")
  669.   print("Press any key to continue...")
  670. end
  671.  
  672. -- Q
  673. shortcut[16] = function()
  674.   quit = true
  675. end
  676.  
  677. -- >>> Object helper functions
  678.  
  679. function clearScreen()
  680.   out.setBackgroundColor(objectColors.background)
  681.   out.setTextColor(objectColors.text)
  682.   out.clear()
  683.   out.setCursorPos(1, 1)
  684. end
  685.  
  686. -- Returns whether the button with the given name
  687. -- has been pressed.
  688. function defaultButtonPressed(name, x, y)
  689.   assert(name)
  690.   assert(x)
  691.   assert(y)
  692.  
  693.   local window = getCurrentWindow()
  694.  
  695.   if (defaultButtons[name]) then
  696.     local button = defaultButtons[name]
  697.     if (x >= button.left and x <= button.right and y >= button.top and y <= button.bottom) then
  698.       return (button.required() or (editMode and not showEditorOptions and currentWindow ~= "mainWindow"))
  699.     end
  700.   else
  701.     return false
  702.   end
  703. end
  704.  
  705. -- Returns the window object that is currently
  706. -- displayed.
  707. function getCurrentWindow()
  708.   if showEditorOptions then
  709.     return editorWindows.children[currentWindow]
  710.   else
  711.     return windows.children[currentWindow]
  712.   end
  713. end
  714.  
  715. -- Used by the List and Selector objects to
  716. -- determine how wide the list should be.
  717. function getLongestString(strings)
  718.   if (strings == nil or #strings == 0) then
  719.     return 0
  720.   end
  721.  
  722.   local ret = 0
  723.  
  724.   for key, value in pairs(strings) do
  725.     length = string.len(value)
  726.     if (length > ret) then
  727.       ret = length
  728.     end
  729.   end
  730.  
  731.   return ret
  732. end
  733.  
  734. -- Checks whether dir is a valid direction-string.
  735. function isValidDirection(dir)
  736.   if (dir ~= nil and
  737.      (dir == "left" or
  738.       dir == "up" or
  739.       dir == "right" or
  740.       dir == "down")) then
  741.     return true
  742.   end
  743.  
  744.   return false
  745. end
  746.  
  747. --[[ Returns a table containing tables for all
  748. files and directories in the given path:
  749. Returns: fileName, filePath, isDir
  750. ]]
  751. function getFileList(path)
  752.   local ret = {}
  753.   local dirList = {}
  754.   local fileList = {}
  755.  
  756.   for file in fs.list(path) do
  757.     if (fs.isDir(file)) then
  758.       table.insert(dirList, dirIndex, file)
  759.       dirIndex = dirIndex + 1
  760.     else
  761.       table.insert(fileList, fileIndex, file)
  762.       fileIndex = fileIndex + 1
  763.     end
  764.   end
  765.   for _, file in ipairs(dirList) do
  766.     table.insert(list, {fileName=file, filePath=fs.combine(path, file), isDir=true})
  767.   end
  768.   for _, file in ipairs(fileList) do
  769.     table.insert(list, {fileName=file, filePath=fs.combine(path, file), isDir=false})
  770.   end
  771.  
  772.   return ret
  773. end
  774.  
  775. -- Returns "horizontal" or "vertical" depending on
  776. -- the given direction.
  777. function getOrientation(direction)
  778.   local orientation = nil
  779.  
  780.   if (direction == "left" or direction == "right") then
  781.     orientation = "horizontal"
  782.   elseif (direction == "up" or direction == "down") then
  783.     orientation = "vertical"
  784.   end
  785.  
  786.   return orientation
  787. end
  788.  
  789. -- Checks whether the eventTypes table contains
  790. -- the given event type.
  791. function eventTypeExists(eventType)
  792.   if (eventType == nil) then
  793.     return true
  794.   end
  795.  
  796.   for _, event in pairs(eventTypes) do
  797.     if (event == eventType) then
  798.       return true
  799.     end
  800.   end
  801.  
  802.   return false
  803. end
  804.  
  805. -- Returns the size that a buffer needs to have
  806. -- to contain all children of a container.
  807. function getNecessaryBufferSize(children, minWidth, minHeight)
  808.   log("getNecessaryBufferSize", "FUNC")
  809.   local width, height = (minWidth or 0), (minHeight or 0)
  810.  
  811.   for _, child in pairs(children) do
  812.     local right = child.width and child.x + child.width or child.x
  813.     local bottom = child.height and child.y + child.height or child.y
  814.    
  815.     width = (right > width) and right or width
  816.     height = (bottom > height) and bottom or height
  817.   end
  818.  
  819.   return width, height
  820. end
  821.  
  822. -- Returns a table containing the position and
  823. -- size that the scroll bar of a ScrollView object
  824. -- should have.
  825. function getScrollBarInfo(pos, containerSize, bufferSize)
  826.   log("getScrollBarInfo", "FUNC")
  827.   log("pos: " .. pos .. ", containerSize: " .. containerSize .. ", bufferSize: " .. bufferSize .. ".", "ATTR")
  828.  
  829.   local ret = {}
  830.   local maxScrollBarHeight = containerSize - 4
  831.  
  832.   local scrollCount = bufferSize - containerSize + 1 -- How often can the user scroll?
  833.   if (scrollCount < 0) then
  834.     scrollCount = 0
  835.   end
  836.  
  837.   local scrollBarSize = math.ceil(maxScrollBarHeight * (1 / (scrollCount + 1)))
  838.   local scrollBarPos
  839.   if (scrollCount == 0) then
  840.     scrollBarPos = 0
  841.   --elseif (pos == scrollCount) then
  842.     --scrollBarPos = maxScrollBarHeight - scrollBarSize
  843.   else
  844.     scrollBarPos = math.floor((maxScrollBarHeight / (scrollCount + 1)) * pos)
  845.   end
  846.  
  847.   ret.size = scrollBarSize
  848.   ret.pos = scrollBarPos
  849.   log("Scrollbar size: " .. scrollBarSize)
  850.   log("Scrollbar pos: " .. scrollBarPos)
  851.  
  852.   return ret
  853. end
  854.  
  855. -- >>> Path
  856.  
  857. Path = {}
  858.  
  859. -- Returns the container which is at the paths
  860. -- nest level.
  861. function Path:getContainerAt(nestLevel)
  862.   local container = getCurrentWindow()
  863.  
  864.   for i = 1, nestLevel do
  865.     if (container.children[self[i]] and container.children[self[i]].isContainer) then
  866.       container = container.children[self[i]]
  867.     else
  868.       return nil -- NOTE: was "break" before
  869.     end
  870.   end
  871.  
  872.   return container
  873. end
  874.  
  875. -- Returns the object which is represented by the
  876. -- path.
  877. function Path:getObject()
  878.   local container = Path.getContainerAt(self, #self - 1)
  879.   return container.children[self[#self]]
  880. end
  881.  
  882. -- Returns the coordinates which are relative to
  883. -- the last container represented by the path.
  884. function Path:getRelativePos(x, y, checkFullPath)
  885.   log("Path.getRelativePos", "FUNC")
  886.   log("x: " .. x .. ", y: " .. y .. ", checkFullPath: " .. tostring(checkFullPath) .. ".", "ATTR")
  887.   local container
  888.   local limit = checkFullPath and #self or #self - 1
  889.  
  890.   for i = 1, limit do
  891.     container = Path.getContainerAt(self, i)
  892.    
  893.     if container then
  894.       x, y = objects.Container.getRelativePos(container, x, y)
  895.     else
  896.       break
  897.     end
  898.   end
  899.  
  900.   return x, y
  901. end
  902.  
  903. --[[ Generates a table of keys representing the
  904. nesting of the containers that have been clicked.
  905. Returns: e.g. {containeID, subContainerID}]]
  906. Path.getContainerPath = function(x, y)
  907.   local containerPath = {}
  908.   local currentContainer = getCurrentWindow()
  909.   local finished = false
  910.   local nestLevel = 1
  911.  
  912.   while not finished do
  913.     finished = true -- Set to true so that the loop stops when no container is found.
  914.     for objectID, object in pairs(currentContainer.children) do
  915.       if finished and object.isContainer then
  916.         if (objects.Container.contentAreaClicked(object, x, y)) then
  917.           containerPath[nestLevel] = objectID
  918.           currentContainer = currentContainer.children[objectID]
  919.           x, y = objects.Container.getRelativePos(currentContainer, x, y)
  920.           finished = false
  921.         end
  922.       end
  923.     end
  924.    
  925.     nestLevel = nestLevel + 1
  926.   end
  927.  
  928.   return containerPath
  929. end
  930.  
  931. -- >>> Buffer
  932.  
  933. Buffer = {
  934.   bufferTable = {};
  935.   width = 0;
  936.   height = 0;
  937. }
  938.  
  939. function Buffer.newPixel(backCol, textCol, char, path)
  940.   local ret = {}
  941.   ret.background = backCol or objectColors.background
  942.   ret.text = textCol or objectColors.text
  943.   ret.char = (type(char) == "string" and char ~= "") and char or " "
  944.   ret.path = path or nil
  945.   if (backCol or textCol or char ~= " " or path) then
  946.     ret.draw = true
  947.   else
  948.     ret.draw = false
  949.   end
  950.  
  951.   return ret;
  952. end
  953.  
  954. function Buffer:new(object)
  955.   object = object or {}
  956.   setmetatable(object, self)
  957.   self.__index = self
  958.   return object
  959. end
  960.  
  961. function Buffer:init(width, height, path, backCol, textCol)
  962.   self.width = width or error("Can't initialize buffer because the width didn't get specified!")
  963.   self.height = height or error("Can't initialize buffer because the height didn't get specified!")
  964.   self.bufferTable = {}
  965.  
  966.   for col = 1, width do
  967.     self.bufferTable[col] = {}
  968.     for row = 1, height do
  969.       self.bufferTable[col][row] = self.newPixel(backCol, textCol, " ", path)
  970.     end
  971.   end
  972. end
  973.  
  974. function Buffer:trim(left, top, right, bottom)
  975.   left = (left < 0) and 0 or left
  976.   top = (top < 0) and 0 or top
  977.   right = (right < 0) and 0 or right
  978.   bottom = (bottom < 0) and 0 or bottom
  979.  
  980.   local width = self.width - left - right
  981.   local height = self.height - top - bottom
  982.   local trimmed = Buffer:new()
  983.   trimmed:init(width, height)
  984.  
  985.   for col = 1, width do
  986.     for row = 1, height do
  987.       trimmed:setPixel(col, row, self.bufferTable[left + col][top + row])
  988.     end
  989.   end
  990.  
  991.   return trimmed
  992. end
  993.  
  994. function Buffer:draw(x, y)
  995.   x = x or 1
  996.   y = y or 1
  997.   local currentPixel
  998.  
  999.   for col = 0, self.width - 1 do
  1000.     for row = 0, self.height - 1 do
  1001.       currentPixel = self.bufferTable[col+1][row+1]
  1002.       if (currentPixel.draw) then
  1003.         out.setCursorPos(x + col, y + row)
  1004.         out.setBackgroundColor(currentPixel.background)
  1005.         out.setTextColor(currentPixel.text)
  1006.         out.write(currentPixel.char)
  1007.         currentPixel.draw = false
  1008.       end
  1009.     end
  1010.   end
  1011. end
  1012.  
  1013. function Buffer:setBackgroundColor(color)
  1014.   for col = 1, width do
  1015.     for row = 1, height do
  1016.       self.bufferTable[col][row].background = color
  1017.     end
  1018.   end
  1019. end
  1020.  
  1021. function Buffer:setTextColor(color)
  1022.   for col = 1, width do
  1023.     for row = 1, height do
  1024.       self.bufferTable[col][row].text = color
  1025.     end
  1026.   end
  1027. end
  1028.  
  1029. function Buffer:setPixel(x, y, pixel)
  1030.   assert(x)
  1031.   assert(y)
  1032.   assert(pixel)
  1033.  
  1034.   if (x > 0 and x <= self.width and y > 0 and y <= self.height) then
  1035.     for key, value in pairs(pixel) do
  1036.       if value then
  1037.         self.bufferTable[x][y][key] = value
  1038.       end
  1039.     end
  1040.   end
  1041. end
  1042.  
  1043. function Buffer:addText(x, y, text)
  1044.   assert(x)
  1045.   assert(y)
  1046.   assert(text)
  1047.  
  1048.   for char in text:gmatch(".") do
  1049.     self:setPixel(x, y, { char=char })
  1050.     x = x + 1
  1051.   end
  1052. end
  1053.  
  1054. function Buffer:addBuffer(x, y, buffer)
  1055.   assert(x)
  1056.   assert(y)
  1057.   assert(buffer)
  1058.   for col = 1, buffer.width do
  1059.     for row = 1, buffer.height do
  1060.       self:setPixel(x + col - 1, y + row - 1, buffer.bufferTable[col][row])
  1061.     end
  1062.   end
  1063. end
  1064.  
  1065. -- Adds a border to the container buffer.
  1066. function Buffer:makeBorder(path, color)
  1067.   local width, height = self.width, self.height
  1068.  
  1069.   self:addBuffer(1, 1, objects.Line.get("horizontal", width, color, path))
  1070.   self:addBuffer(1, height, objects.Line.get("horizontal", width, color, path))
  1071.   self:addBuffer(1, 1, objects.Line.get("vertical", height, color, path))
  1072.   self:addBuffer(width, 1, objects.Line.get("vertical", height, color, path))
  1073. end
  1074.  
  1075. -- >>> Objects
  1076.  
  1077. objects = {}
  1078. -- Returns a new object with its default attributes
  1079. -- depending on the object type.
  1080. -- Note: Don't call this function, use objects.create!
  1081. function objects.new(objectType, x, y)
  1082.   log("objects.new", "FUNC")
  1083.  
  1084.   local object = {}
  1085.   local path = Path.getContainerPath(x, y)
  1086.   local relativeX, relativeY = Path.getRelativePos(path, x, y, true)
  1087.  
  1088.   object.objID = "new" .. objectType
  1089.   object.objType = objectType
  1090.   object.path = path
  1091.   object.x = relativeX
  1092.   object.y = relativeY
  1093.   object.absoluteX = x
  1094.   object.absoluteY = y
  1095.  
  1096.   local maxWidth = maxX - x
  1097.   local maxHeight = maxY - y
  1098.  
  1099.   if (objects[objectType] and objects[objectType].new) then
  1100.     objects[objectType].new(object, maxWidth, maxHeight)
  1101.   elseif (objects.Container[objectType]) then
  1102.     objects.Container.new(object, maxWidth, maxHeight)
  1103.   else
  1104.     objects["Unknown"].new(objectType)
  1105.   end
  1106.  
  1107.   return object
  1108. end
  1109.  
  1110. function objects.create(objType, x, y)
  1111.   local object = objects.new(objType, x, y)
  1112.   local container = Path.getContainerAt(object.path, #object.path)
  1113.   local key = objects.Container.getNextFreeKey(container)
  1114.   table.insert(object.path, key)
  1115.   table.insert(container.children, key, object)
  1116.  
  1117.   windowBuffer:addBuffer(x, y, objects.get(object))
  1118.   objects.draw(object)
  1119. end
  1120.  
  1121. function objects.getPosModifier(self)
  1122.   assert(self)
  1123.  
  1124.   local x, y = 0, 0
  1125.  
  1126.   if (#self.path > 1) then
  1127.     for nestLevel = 1, #self.path - 1 do
  1128.       local object = Path.getContainerAt(self.path, nestLevel)
  1129.       local modX, modY = objects.Container.getPosModifier(object, x, y)
  1130.       x, y = x + modX, y + modY
  1131.     end
  1132.   end
  1133.  
  1134.   return x, y
  1135. end
  1136.  
  1137. -- Returns the relative position of the object
  1138. -- including the possible modifications of its
  1139. -- containers.
  1140. function objects.getRelativePos(self)
  1141.   assert(self)
  1142.  
  1143.   local x, y = self.x, self.y
  1144.   local modX, modY = objects.getPosModifier(self)
  1145.  
  1146.   return x + modX, y + modY
  1147. end
  1148.  
  1149. -- Returns the absolute position of the object
  1150. -- including the possible modifications of its
  1151. -- containers.
  1152. function objects.getAbsolutePos(self)
  1153.   assert(self)
  1154.  
  1155.   local x, y = self.absoluteX, self.absoluteY
  1156.   local modX, modY = objects.getPosModifier(self)
  1157.  
  1158.   return x + modX, y + modY
  1159. end
  1160.  
  1161. -- Returns the buffer of the object.
  1162. function objects.get(self, param)
  1163.   assert(self)
  1164.  
  1165.   log("objects.get", "FUNC")
  1166.   log(self.objType .. " ID: \"" .. self.objID .. "\", param: \"" .. tostring(param) .. "\"", "ATTR")
  1167.  
  1168.   local objType = self.objType or "Unknown"
  1169.   if (self.isContainer) then
  1170.     return objects.Container.get(self, param)
  1171.   elseif (objects[objType] and objects[objType].get) then
  1172.     return objects[objType].get(self, param)
  1173.   else
  1174.     error("Can't get buffer of object type \"" .. objType .. "\"!")
  1175.   end
  1176. end
  1177.  
  1178. --[[ Used to return the buffer of an object which
  1179. shows the user where to click to interact with
  1180. the object in a certain way.
  1181. (e.g. scrolling the ScrollView)
  1182. ]]
  1183. function objects.addMarker(self, buffer)
  1184.   assert(self)
  1185.  
  1186.   log("objects.addMarker", "FUNC")
  1187.   log(self.objType .. " ID: \"" .. self.objID .. "\".", "ATTR")
  1188.  
  1189.   if (objects[self.objType] and objects[self.objType].addMarker) then
  1190.     objects[self.objType].addMarker(self, buffer)
  1191.   elseif (objects.Container[self.objType] and objects.Container[self.objType].addMarker) then
  1192.     objects.Container[self.objType].addMarker(self, buffer)
  1193.   end
  1194. end
  1195.  
  1196. function objects.remove(self)
  1197.   assert(self)
  1198.  
  1199.   log("objects.remove", "FUNC")
  1200.   log(self.objType .. " ID: \"" .. self.objID .. "\".")
  1201.  
  1202.   local path = self.path
  1203.   local objKey = path[#path]
  1204.   local container = Path.getContainerAt(self.path, #self.path - 1)
  1205.  
  1206.   container.children[objKey] = nil
  1207.  
  1208.   for i = #path - 2, 0, -1 do
  1209.     local parentContainer = Path.getContainerAt(self.path, i)
  1210.     parentContainer.children[path[i + 1]] = container
  1211.     container = parentContainer
  1212.   end
  1213.  
  1214.   windows.children[currentWindow] = container
  1215. end
  1216.  
  1217. -- Draws the object.
  1218. function objects.draw(self, param, drawMarker)
  1219.   assert(self)
  1220.  
  1221.   log("objects.draw", "FUNC")
  1222.   log(self.objType .. " ID: " .. self.objID .. ", param: \"" .. tostring(param) .. "\"", "ATTR")
  1223.  
  1224.   local buffer = objects.get(self, param)
  1225.   if drawMarker then
  1226.     objects.addMarker(self, buffer)
  1227.   end
  1228.  
  1229.   if (#self.path > 1) then
  1230.     local container = getCurrentWindow()
  1231.     local x, y = self.absoluteX, self.absoluteY
  1232.     objects.Container.drawBuffer(container, buffer, self.path, x, y, x, y, 0)
  1233.   else
  1234.     buffer:draw(self.x, self.y)
  1235.   end
  1236.  
  1237.   drawDefaultButtons()
  1238. end
  1239.  
  1240. -- Returns the resulting event and a "params"-array.
  1241. function objects.click(self, x, y)
  1242.   assert(self)
  1243.  
  1244.   log("objects.click", "FUNC")
  1245.   log(self.objType .. " ID: " .. self.objID .. ", x: " .. x .. ", y: " .. y .. ".", "ATTR")
  1246.  
  1247.   local objType = self.objType
  1248.   local event, params
  1249.   if objects[objType] and objects[objType].click then
  1250.     event, params = objects[objType].click(self, x, y)
  1251.   elseif (self.isContainer and objects.Container[objType] and objects.Container[objType].click) then
  1252.     event, params = objects.Container[objType].click(self, x, y)
  1253.   end
  1254.  
  1255.   return event, params
  1256. end
  1257.  
  1258. -- Gets called when an object gets clicked in edit
  1259. -- mode. Returns false if the object should be
  1260. -- edited as usual (move, scale).
  1261. function objects.editorClick(self, x, y)
  1262.   assert(self)
  1263.   assert(x)
  1264.   assert(y)
  1265.  
  1266.   log("objects.editorClick", "FUNC")
  1267.   log(self.objType .. " ID: \"" .. self.objID .. "\", x: " .. x .. ", y: " .. y .. ".", "ATTR")
  1268.  
  1269.   if (objects[self.objType] and objects[self.objType].editorClick) then
  1270.     return objects[self.objType].editorClick(self, x, y)
  1271.   elseif (objects.Container[self.objType] and objects.Container[self.objType].editorClick) then
  1272.     return objects.Container[self.objType].editorClick(self, x, y)
  1273.   else
  1274.     return false
  1275.   end
  1276. end
  1277.  
  1278. -- Moves the object.
  1279. function objects.move(self, addX, addY)
  1280.   assert(self)
  1281.  
  1282.   log("objects.move", "FUNC")
  1283.   log(self.objType .. " ID: " .. self.objID .. ", x: " .. x .. ", y: " .. y .. ".", "ATTR")
  1284.  
  1285.   local objType = self.objType
  1286.  
  1287.   if (objects[objType] and objects[objType].move) then
  1288.     objects[objType].move(self, addX, addY)
  1289.   elseif (self.isContainer) then
  1290.     self.x = self.x + addX
  1291.     self.y = self.y + addY
  1292.     objects.Container.move(self, addX, addY)
  1293.   else
  1294.     self.x = self.x + addX
  1295.     self.y = self.y + addY
  1296.     self.absoluteX = self.absoluteX + addX
  1297.     self.absoluteY = self.absoluteY + addY
  1298.   end
  1299. end
  1300.  
  1301. -- Scales the object.
  1302. function objects.scale(self, x, y)
  1303.   assert(self)
  1304.  
  1305.   log("objects.scale", "FUNC")
  1306.   log(self.objType .. " ID: " .. self.objID .. ", x: " .. x .. ", y: " .. y .. ".", "ATTR")
  1307.   log("Object pos: " .. self.x .. ", " .. self.y, "DEBUG")
  1308.  
  1309.   local objType = self.objType
  1310.  
  1311.   if (objects[objType] and objects[objType].scale) then
  1312.     objects[objType].scale(self, x, y)
  1313.   elseif (self.isContainer and objects.Container[objType].scale) then
  1314.     objects.Container[objType].scale(self, x, y)
  1315.   else
  1316.     local objX, objY = self.x, self.y
  1317.     if (x > objX and y >= objY) then
  1318.       self.width = x - objX + 1
  1319.       self.height = y - objY + 1
  1320.     end
  1321.   end
  1322. end
  1323.  
  1324. --[[ Returns the position of the pixel which shows
  1325. the user where to click when he wants to move the
  1326. object.]]
  1327. function objects.getMovePos(self)
  1328.   local objType = self.objType
  1329.   if (objects[objType] and objects[objType].getMovePos) then
  1330.     return objects[objType].getMovePos(self)
  1331.   elseif (self.isContainer and objects.Container[objType] and objects.Container[objType].getMovePos) then
  1332.     return objects.Container[objType].getMovePos(self)
  1333.   else
  1334.     return objects.getAbsolutePos(self)
  1335.   end
  1336. end
  1337.  
  1338. --[[ Returns the position of the pixel which shows
  1339. the user where to click when he wants to scale the
  1340. object.]]
  1341. function objects.getScalePos(self)
  1342.   if not self.canScale then
  1343.     error("Tried to get scale pos of an unscalable object!")
  1344.   end
  1345.  
  1346.   local objType = self.objType
  1347.   if (objects[objType] and objects[objType].getScalePos) then
  1348.     return objects[objType].getScalePos(self)
  1349.   elseif (self.isContainer and objects.Container[objType].getScalePos) then
  1350.     objects.Container[objType].getScalePos(self)
  1351.   else
  1352.     local x, y = objects.getAbsolutePos(self)
  1353.     return x + self.width - 1, y + self.height - 1
  1354.   end
  1355. end
  1356.  
  1357. -- Returns the left, top, right and bottom coordinates of the object.
  1358. function objects.getDimensions(self)
  1359.   local objType = self.objType
  1360.   if (objects[objType] and objects[objType].getDimensions) then
  1361.     return objects[objType].getDimensions(self)
  1362.   elseif (self.isContainer and objects.Container[objType] and objects.Container[objType].getDimensions) then
  1363.     return objects.Container[objType].getDimensions(self)
  1364.   else
  1365.     local left, top, right, bottom
  1366.     left, top = self.x, self.y
  1367.     right = left + self.width - 1
  1368.     bottom = top + self.height - 1
  1369.     return left, top, right, bottom
  1370.   end
  1371. end
  1372.  
  1373. -- >> Unknown
  1374. objects.Unknown = {}
  1375. objects.Unknown.new = function(objType)
  1376.   objType = objType and " \"" .. objType .. "\"" or ""
  1377.   error("Tried to create new object with unknown object ID" .. objType .. "!")
  1378. end
  1379.  
  1380. objects.Unknown.get = function(self)
  1381.   error("Tried to get the buffer of and unknown object!")
  1382. end
  1383.  
  1384. -- >> Button
  1385. objects.Button = {}
  1386. objects.Button.new = function(self, maxWidth, maxHeight)
  1387.   self.width = (maxWidth < size.Button.width) and maxWidth or size.Button.width
  1388.   self.height = (maxHeight < size.Button.height) and maxHeight or size.Button.height
  1389.   self.widthPercent = maxX / self.width
  1390.   self.heightPercent = maxY / self.height
  1391.   self.text = "Button"
  1392.   self.funcType = ""
  1393.   self.param = ""
  1394.   self.canScale = true
  1395.   self.canClick = true
  1396. end
  1397.  
  1398. objects.Button.get = function(self, buttonColor)
  1399.   local width = self.width
  1400.   local height = self.height
  1401.   local path = self.path
  1402.   local text = self.text
  1403.   local color
  1404.  
  1405.   if buttonColor then
  1406.     color = buttonColor
  1407.   elseif (toggleState[self.objID]) then
  1408.     color = objectColors.Button.active
  1409.   else
  1410.     color = objectColors.Button.default
  1411.   end
  1412.  
  1413.   local buffer = Buffer:new()
  1414.   local textCol = math.floor((width - string.len(text)) / 2) + 1
  1415.   local textRow = math.ceil(height / 2)
  1416.   buffer:init(width, height, path, color, objectColors.Button.text)
  1417.   buffer:addText(textCol, textRow, text)
  1418.  
  1419.   return buffer
  1420. end
  1421.  
  1422. -- Returns: "button_clicked" event, objID
  1423. objects.Button.click = function(self, x, y)
  1424.   assert(self)
  1425.  
  1426.   local actionType = self.funcType
  1427.   local param = self.param
  1428.  
  1429.   if (actionType == "switch") then
  1430.     drawWindow(param)
  1431.     return nil, nil
  1432.   elseif (actionType == "function") then
  1433.     if changeButtonColor then
  1434.       objects.draw(self, objectColors.Button.active)
  1435.     end
  1436.    
  1437.     if userFunctions[param] then
  1438.       userFunctions[param](true)
  1439.     elseif editorFunctions[param] then
  1440.       editorFunctions[param](true)
  1441.     end
  1442.    
  1443.     if changeButtonColor then
  1444.       objects.draw(self, objectColors.Button.default)
  1445.     else
  1446.       changeButtonColor = true
  1447.     end
  1448.    
  1449.     return "button_clicked", {self.objID, true}
  1450.   elseif (actionType == "toggle function") then
  1451.     local state = not toggleState[self.objID]
  1452.    
  1453.     toggleState[self.objID] = state
  1454.     objects.draw(self)
  1455.    
  1456.     if userFunctions[param] then
  1457.       userFunctions[param](state)
  1458.     elseif editorFunctions[param] then
  1459.       editorFunctions[param](state)
  1460.     end
  1461.    
  1462.     return "button_toggled", {self.objID, state}
  1463.   else
  1464.     log("Unknown function type: \"" .. actionType .. "\"", "WARNING")
  1465.   end
  1466. end
  1467.  
  1468. -- >> Text
  1469. objects.Text = {}
  1470. objects.Text.new = function(self)
  1471.   self.text = "newText"
  1472.   self.width = #self.text
  1473.   self.height = 1
  1474. end
  1475.  
  1476. objects.Text.get = function(self)
  1477.   local width = #self.text
  1478.   local buffer = Buffer:new()
  1479.   buffer:init(width, 1, self.path)
  1480.   buffer:addText(1, 1, self.text)
  1481.  
  1482.   return buffer
  1483. end
  1484.  
  1485. objects.Text.draw = function(self)
  1486.   local x = self.x
  1487.   local y = self.y
  1488.   local text = self.text
  1489.   out.setCursorPos(x, y)
  1490.   out.write(text)
  1491. end
  1492.  
  1493. objects.Text.getDimensions = function(self)
  1494.   local left, top, right, bottom = self.x, self.y, 1, self.y
  1495.   right = left + string.len(self.text) - 1
  1496.   return left, top, right, bottom
  1497. end
  1498.  
  1499. -- >> Variable
  1500. objects.Variable = {}
  1501. objects.Variable.new = function(self)
  1502.   self.width = 1
  1503.   self.height = 1
  1504. end
  1505.  
  1506. objects.Variable.get = function(self, value)
  1507.   local buffer = Buffer:new()
  1508.  
  1509.   if value then
  1510.     buffer:init(string.len(value), 1, self.path)
  1511.     buffer:addText(1, 1, value)
  1512.   else
  1513.     buffer:init(1, 1, self.path)
  1514.   end
  1515.  
  1516.   return buffer
  1517. end
  1518.  
  1519. -- >> ProgressBar
  1520. objects.ProgressBar = {}
  1521. objects.ProgressBar.new = function(self, maxWidth)
  1522.   self.length = (maxWidth < size.ProgressBar.length) and maxWidth or size.ProgressBar.length
  1523.   self.lengthPercent = maxX / self.length
  1524.   self.width = self.length
  1525.   self.height = 1
  1526.   self.direction = "right"
  1527.   self.objID = "newProgressBar"
  1528.   self.canScale = true
  1529. end
  1530.  
  1531. objects.ProgressBar.get = function(self, value)
  1532.   local length = self.length
  1533.   local direction = (isValidDirection(self.direction)) and self.direction or "right"
  1534.   local orientation = getOrientation(direction) or error("Direction " .. direction .. " is invalid!")
  1535.   value = value or progressBarValues[self.objID]
  1536.  
  1537.   if (orientation == "horizontal") then
  1538.     width, height = length, 1
  1539.   else
  1540.     width, height = 1, length
  1541.   end
  1542.  
  1543.   local buffer = Buffer:new()
  1544.   buffer:init(width, height, self.path, objectColors.ProgressBar.background)
  1545.  
  1546.   if value then
  1547.     local color
  1548.     if (value < 33) then
  1549.       color = objectColors.ProgressBar.low
  1550.     elseif (value > 66) then
  1551.       color = objectColors.ProgressBar.high
  1552.     else
  1553.       color = objectColors.ProgressBar.medium
  1554.     end
  1555.    
  1556.     local filled = math.floor((length / 100) * value)
  1557.     local valueBuffer = objects.Line.get(getOrientation(direction), filled, color)
  1558.     local addX, addY = 1, 1
  1559.    
  1560.     if (direction == "left") then
  1561.       addX = width - filled
  1562.     elseif (direction == "up") then
  1563.       addY = height - filled
  1564.     end
  1565.    
  1566.     buffer:addBuffer(addX, addY, valueBuffer)
  1567.   end
  1568.  
  1569.   return buffer
  1570. end
  1571.  
  1572. objects.ProgressBar.getMovePos = function(self)
  1573.   local dir = self.direction
  1574.   local length = self.length
  1575.   local x, y = objects.getAbsolutePos(self)
  1576.  
  1577.   if (dir == "left") then
  1578.     return x + length - 1, y
  1579.   elseif (dir == "up") then
  1580.     return x, y + length - 1
  1581.   else
  1582.     return x, y
  1583.   end
  1584. end
  1585.  
  1586. objects.ProgressBar.getScalePos = function(self)
  1587.   local dir = self.direction
  1588.   local length = self.length
  1589.   local x, y = objects.getAbsolutePos(self)
  1590.  
  1591.   if (dir == "right") then
  1592.     return x + length - 1, y
  1593.   elseif (dir == "down") then
  1594.     return x, y + length - 1
  1595.   else
  1596.     return x, y
  1597.   end
  1598. end
  1599. -- Move and scale pos is wrong when the progress bar goes up.
  1600. objects.ProgressBar.scale = function(self, x, y)
  1601.   local moveX, moveY = objects.getMovePos(self)
  1602.   local relMoveX, relMoveY = Path.getRelativePos(self.path, moveX, moveY)
  1603.   local length
  1604.   local newX, newY
  1605.   local direction
  1606.   local width, height
  1607.  
  1608.   if (x < relMoveX and y == relMoveY) then -- Clicked left of the progressBar.
  1609.     length = relMoveX - x + 1
  1610.     direction = "left"
  1611.     newX, newY = x, y
  1612.     width, height = length, 1
  1613.   elseif (x == relMoveX and y < relMoveY) then -- Clicked above the progressBar.
  1614.     length = relMoveY - y + 1
  1615.     direction = "up"
  1616.     newX, newY = x, y
  1617.     width, height = 1, length
  1618.   elseif (x > relMoveX and y == relMoveY) then -- Clicked right of the progressBar.
  1619.     length = x - relMoveX + 1
  1620.     direction = "right"
  1621.     newX, newY = relMoveX, relMoveY
  1622.     width, height = length, 1
  1623.   elseif (x == relMoveX and y > relMoveY) then -- Clicked below the progressBar.
  1624.     length = y - relMoveY + 1
  1625.     direction = "down"
  1626.     newX, newY = relMoveX, relMoveY
  1627.     width, height = 1, length
  1628.   else
  1629.     return
  1630.   end
  1631.  
  1632.   if (length > 2) then
  1633.     local xDiff, yDiff = newX - self.x, newY - self.y
  1634.    
  1635.     self.absoluteX, self.absolutey = self.absoluteX + xDiff, self.absoluteY + yDiff
  1636.     self.x, self.y = newX, newY
  1637.     self.direction = direction
  1638.     self.width, self.height = width, height
  1639.     self.length = length
  1640.     self.lengthPercent = length / maxX
  1641.   end
  1642. end
  1643.  
  1644. -- >> Input
  1645. objects.Input = {}
  1646. objects.Input.new = function(self)
  1647.   self.message = "Enter something."
  1648.   self.isPassword = false
  1649.   self.width = 2
  1650.   self.height = 1
  1651.   self.canClick = true
  1652. end
  1653.  
  1654. objects.Input.get = function(self)
  1655.   local userInput = userInputs[self.objID] or ""
  1656.   local width, height = 2 + string.len(userInput), 1
  1657.  
  1658.   local buffer = Buffer:new()
  1659.   buffer:init(width, height, self.path, objectColors.Input.default)
  1660.   if userInput ~= "" then
  1661.     buffer:addText(userInput)
  1662.   end
  1663.  
  1664.   return buffer
  1665. end
  1666.  
  1667. -- Returns: "text_changed" event, objID, text
  1668. objects.Input.click = function(self)
  1669.   local x = self.x
  1670.   local y = self.y
  1671.   local objID = self.objID
  1672.   local message = self.message
  1673.   local isPassword = (self.isPassword == nil) and false or self.isPassword
  1674.   local maxLength = self.maxLength
  1675.   local existingInput = userInputs[objID]
  1676.  
  1677.   out.setBackgroundColor(objectColors.background)
  1678.   out.setCursorPos(x, y)
  1679.   if (existingInput ~= nil) then -- Clear the text on the input object.
  1680.     out.write(string.rep(" ", string.len(existingInput) + 2))
  1681.   else
  1682.     out.write("  ")
  1683.   end
  1684.   userInputs[objID] = nil
  1685.  
  1686.   out.setCursorPos(x, y)
  1687.   if not outIsTerm then
  1688.     -- make the input-object yellow
  1689.     out.setBackgroundColor(objectColors["Input"].active)
  1690.     out.write("  ")
  1691.     out.setBackgroundColor(objectColors.background)
  1692.   end
  1693.  
  1694.   if outIsTerm then
  1695.     out.setCursorPos(x + 1, y)
  1696.   end
  1697.  
  1698.   local userInput = readUserInput(message, isPassword)
  1699.   if (userInput ~= nil) then
  1700.     userInputs[objID] = userInput
  1701.   end
  1702.  
  1703.   out.setCursorPos(x, y)
  1704.   out.setBackgroundColor(objectColors.Input.default)
  1705.   out.setTextColor(objectColors.Input.text)
  1706.  
  1707.   out.write(" ")
  1708.   if (userInput ~= nil and userInput ~= "") then
  1709.     if isPassword then
  1710.       for i = 1, string.len(userInput) do
  1711.         out.write("*")
  1712.       end
  1713.     else
  1714.       out.write(userInput)
  1715.     end
  1716.   end
  1717.  
  1718.   out.write(" ")
  1719.   out.setBackgroundColor(objectColors.background)
  1720.   out.setTextColor(objectColors.text)
  1721.  
  1722.   return "text_changed", {self.objID, userInput}
  1723. end
  1724.  
  1725. -- >> List
  1726. objects.List = {}
  1727. objects.List.new = function(self)
  1728.   self.elements = userLists.testList
  1729.   self.objID = "testList"
  1730.   self.isMultiselect = false
  1731.   self.canClick = true
  1732. end
  1733.  
  1734. objects.List.get = function(self)
  1735.   local buffer = Buffer:new()
  1736.   local elements = self.elements or { "empty" }
  1737.  
  1738.   if (type(elements) == "string") then
  1739.     elements = { [1]=self.elements }
  1740.   end
  1741.  
  1742.   -- If there's a list which has this list's first element as its key.
  1743.   if (#elements == 1 and userLists[elements[1]]) then
  1744.     self.elements = userLists[self.elements[1]]
  1745.   end
  1746.  
  1747.   elements = self.elements
  1748.  
  1749.   local width = getLongestString(elements) + 2
  1750.   self.width = width
  1751.   local height = #elements
  1752.   self.height = height
  1753.   local objID = self.objID
  1754.   local isMultiselect = self.isMultiselect
  1755.   if not selectedItems[objID] then
  1756.     selectedItems[objID] = {}
  1757.   end
  1758.  
  1759.   buffer:init(width, height, self.path, objectColors.List.default)
  1760.  
  1761.   local line = 1
  1762.   for key, element in pairs(elements) do
  1763.     if (selectedItems[objID][key]) then
  1764.       buffer:addBuffer(1, line, objects.Line.get("horizontal", width, objectColors.List.active))
  1765.     end
  1766.     buffer:addText(2, line, elements[line])
  1767.     line = line + 1
  1768.   end
  1769.  
  1770.   return buffer
  1771. end
  1772.  
  1773. -- Returns: "selection_changed" event, objID, key, true or false
  1774. objects.List.click = function(self, x, y)
  1775.   local objID = self.objID
  1776.   local isMultiselect = self.isMultiselect
  1777.   local itemSelected = selectedItems[objID][y]
  1778.  
  1779.   if (isMultiselect) then
  1780.     selectedItems[objID][y] = not itemSelected
  1781.   else
  1782.     selectedItems[objID] = {}
  1783.     selectedItems[objID][y] = true
  1784.   end
  1785.  
  1786.   objects.draw(self)
  1787.  
  1788.   return "selection_changed", {self.objID, y, selectedItems[objID][y]}
  1789. end
  1790.  
  1791. objects.List.getFirstSelectedKey = function(self)
  1792.   local objID = self.objID
  1793.  
  1794.   for key, value in pairs(selectedItems[objID]) do
  1795.     if (value == true) then
  1796.       return key
  1797.     end
  1798.   end
  1799.  
  1800.   return nil
  1801. end
  1802.  
  1803. -- >> File Selector
  1804. objects.FileSelector = {}
  1805. objects.FileSelector.new = function(self)
  1806.   self.width = string.len(text.fileSelector)
  1807.   self.height = 1
  1808.   self.isMultiselect = false
  1809.   self.canClick = true
  1810. end
  1811.  
  1812. objects.FileSelector.draw = function(self)
  1813.   local objectID = objectID
  1814.   local x = self.x
  1815.   local y = self.y
  1816.   local isMultiselect = self.isMultiselect
  1817.  
  1818.   out.setBackgroundColor(objectColors["FileSelector"].default)
  1819.   out.setTextColor(objectColors["FileSelector"].text)
  1820.  
  1821.   out.setCursorPos(x, y)
  1822.   out.write(text.fileSelector)
  1823.  
  1824.   if (selectedFiles[objectID] ~= nil) then
  1825.     out.setBackgroundColor(objectColors.background)
  1826.     out.setTextColor(objectColors.text)
  1827.     local files = selectedFiles[objectID]
  1828.     out.write(" ")
  1829.     if (type(files) == "table") then
  1830.       local sep = ""
  1831.       for _, fileName in pairs(files) do
  1832.         term.write(sep .. fileName)
  1833.         sep = ", "
  1834.       end
  1835.     else
  1836.       out.write(files)
  1837.     end
  1838.   end
  1839.  
  1840.   out.setBackgroundColor(objectColors.background)
  1841.   out.setTextColor(objectColors.text)
  1842. end
  1843.  
  1844. objects.FileSelector.click = function(self, x, y)
  1845. error("Not yet implemented")
  1846. -- TODO
  1847.   local finished = false
  1848.   local path = "/"
  1849.   local list = {}
  1850.  
  1851.   while not finished do
  1852.     clearScreen()
  1853.     out.setCursorPos(2, 1)
  1854.     out.write("Path: " .. path)
  1855.    
  1856.     list = getFileList(path)
  1857.    
  1858.     out.setTextColor(objectColors.FileSelector.text)
  1859.   end
  1860. end
  1861.  
  1862. -- >>> Containers
  1863.  
  1864. objects.Container = {}
  1865.  
  1866. objects.Container.getNextFreeKey = function(self)
  1867.   local nextKey = 1
  1868.  
  1869.   while self.children[nextKey] ~= nil do
  1870.     nextKey = nextKey + 1
  1871.   end
  1872.  
  1873.   return nextKey
  1874. end
  1875.  
  1876. -- Returns the area of the container which stores
  1877. -- its children.
  1878. objects.Container.getContentArea = function(self)
  1879.   assert(self)
  1880.   local objType = self.objType
  1881.  
  1882.   if (objects.Container[objType].getContentArea) then
  1883.     return objects.Container[objType].getContentArea(self)
  1884.   else
  1885.     local left = self.x + 1
  1886.     local top = self.y + 1
  1887.     local right = self.x + self.width - 2
  1888.     local bottom = self.y + self.height - 2
  1889.     return left, top, right, bottom
  1890.   end
  1891. end
  1892.  
  1893. -- Determines whether the container itself or its
  1894. -- content area is at the given position.
  1895. objects.Container.contentAreaClicked = function(self, x, y)
  1896.   local left, top, right, bottom = objects.Container.getContentArea(self)    
  1897.  
  1898.   return (x >= left and x <= right and y >= top and y <= bottom)
  1899. end
  1900.  
  1901. -- Draws the buffer and trims it before that if
  1902. -- necessary.
  1903. objects.Container.drawBuffer = function(self, buffer, path, x, y, absoluteX, absoluteY, nestLevel)
  1904.   log("objects.Container.drawBuffer", "FUNC")
  1905.   local modX, modY = objects.Container.getPosModifier(self, x, y)
  1906.   x, y = x + modX, y + modY
  1907.  
  1908.   if (self.objType ~= "Window") then
  1909.     local containerLeft, containerTop, containerRight, containerBottom = 1, 1, self.width - 1, self.height - 1
  1910.     local objLeft, objTop, objRight, objBottom = x, y, x + buffer.width - 1, y + buffer.height - 1
  1911.    
  1912.     --log("Container dimensions: left: " .. containerLeft .. ", top: " .. containerTop .. ", right: " .. containerRight .. ", bottom: " ..containerBottom .. ".", "DEBUG")
  1913.     --log("Object dimensions: left: " .. objLeft .. ", top: " .. objTop .. ", right: " .. objRight .. ", bottom: " ..objBottom .. ".", "DEBUG")
  1914.    
  1915.     if (objLeft > self.width or
  1916.         objTop > self.height or
  1917.         objRight < 0 or
  1918.         objBottom < 0) then
  1919.       return
  1920.     elseif (objLeft < containerLeft or objTop < containerTop or objRight >= containerRight or objBottom >= containerBottom) then
  1921.       -- Object goes over the border. Trim it.
  1922.       local trimLeft = (containerLeft - objLeft) > 0 and containerLeft - objLeft or 0
  1923.       local trimTop = (containerTop - objTop) > 0 and containerTop - objTop or 0
  1924.       local trimRight = (objRight - containerRight + 1) > 0 and objRight - containerRight + 1 or 0
  1925.       local trimBottom = (objBottom - containerBottom + 1) > 0 and objBottom - containerBottom + 1 or 0
  1926.       buffer = buffer:trim(trimLeft, trimTop, trimRight, trimBottom)
  1927.      
  1928.       if (trimLeft > 0) then
  1929.         x = x + trimLeft
  1930.         absoluteX = absoluteX + trimLeft
  1931.       end
  1932.       if (trimTop > 0) then
  1933.         y = y + trimTop
  1934.         absoluteY = absoluteY + trimTop
  1935.       end
  1936.     end
  1937.   end
  1938.  
  1939.   if (nestLevel < #path - 1) then
  1940.     local container = Path.getContainerAt(path, nestLevel + 1)
  1941.     local relX, relY = objects.Container.getRelativePos(container, x, y)
  1942.     objects.Container.drawBuffer(container, buffer, path, relX, relY, absoluteX, absoluteY, nestLevel + 1)
  1943.   else
  1944.     buffer:draw(absoluteX + modX, absoluteY + modY)
  1945.   end
  1946. end
  1947.  
  1948. objects.Container.getRelativePos = function(self, x, y)
  1949.   local left, top, right, bottom = objects.Container.getContentArea(self)
  1950.  
  1951.   --log("objects.Container.getRelativePos", "FUNC")
  1952.   --log(self.objType .. " ID: " .. self.objID .. ", x: " .. x .. ", y: " .. y .. ".", "ATTR")
  1953.   --log("Left: " .. left .. ", top: " .. top .. ", right: " .. right .. ", bottom: " .. bottom .. ".", "DEBUG")
  1954.  
  1955.   retX = x - left + 1
  1956.   retY = y - top + 1
  1957.  
  1958.   return retX, retY
  1959. end
  1960.  
  1961. objects.Container.getParentsRelativePos = function(self, x, y)
  1962.   local left, top, right, bottom = objects.Container.getContentArea(self)
  1963.  
  1964.   return x + left - 1, y + top - 1
  1965. end
  1966.  
  1967. objects.Container.new = function(self, maxWidth, maxHeight)
  1968.   log("objects.Container.new", "FUNC")
  1969.   local defaultWidth, defaultHeight = size.Container.width, size.Container.height
  1970.  
  1971.   self.isContainer = true
  1972.   self.canScale = true
  1973.   self.children = {}
  1974.   self.width = (maxWidth < defaultWidth) and maxWidth or defaultWidth
  1975.   self.height = (maxHeight < defaultHeight) and maxHeight or defaultHeight
  1976.  
  1977.   log("New container dimensioins: " .. self.width .. " x " .. self.height .. ".")
  1978.  
  1979.   if (objects.Container[self.objType].new) then
  1980.     objects.Container[self.objType].new(self)
  1981.   end
  1982. end
  1983.  
  1984. objects.Container.get = function(self)
  1985.   log("Container dimensions: " .. (self.width or "unknown") .. " x " .. (self.height or "unknown") .. ".")
  1986.  
  1987.   local left, top, right, bottom = objects.Container.getContentArea(self)
  1988.   local minWidth, minHeight = right - left + 1, bottom - top + 1
  1989.   local width, height = getNecessaryBufferSize(self.children, minWidth, minHeight)
  1990.  
  1991.   local buffer = Buffer:new()
  1992.   buffer:init(width, height, {})
  1993.  
  1994.   for objectID, object in pairs(self.children) do
  1995.     local objectBuffer = objects.get(object)
  1996.     buffer:addBuffer(object.x, object.y, objectBuffer)
  1997.   end
  1998.  
  1999.   return objects.Container[self.objType].get(self, buffer)
  2000. end
  2001.  
  2002. objects.Container.getPosModifier = function(self, x, y)
  2003.   assert(self)
  2004.  
  2005.   log("objects.Container.getPosModifier", "FUNC")
  2006.   log("Object type: " .. tostring(self.objType) .. ", ID: " .. tostring(self.objID) .. ".", "INFO")
  2007.   log("Position: " .. x .. ", " .. y .. ".", "INFO")
  2008.  
  2009.   local objType = self.objType
  2010.   if (objects.Container[objType] and objects.Container[objType].getPosModifier) then
  2011.     local newX, newY = objects.Container[objType].getPosModifier(self, x, y)
  2012.     log("Modifier: " .. newX .. ", " .. newY .. ".", "INFO")
  2013.     return newX, newY
  2014.   else
  2015.     log("Position not modified.")
  2016.     return 0, 0
  2017.   end
  2018. end
  2019.  
  2020. objects.Container.move = function(self, addX, addY)
  2021.   self.absoluteX = self.absoluteX + addX
  2022.   self.absoluteY = self.absoluteY + addY
  2023.  
  2024.   for _, child in pairs(self.children) do
  2025.     if child.isContainer then
  2026.       objects.Container.move(child, addX, addY)
  2027.     else
  2028.       child.absoluteX = child.absoluteX + addX
  2029.       child.absoluteY = child.absoluteY + addY
  2030.     end
  2031.   end
  2032. end
  2033.  
  2034. -- >> Window
  2035.  
  2036. objects.Container.Window = {}
  2037. objects.Container.Window.new = function(windowName)
  2038.   local object = {}
  2039.   object.objType = "Window"
  2040.   object.parent = "mainWindow"
  2041.   object.children = {}
  2042.   object.width, object.height = maxX, maxY
  2043.  
  2044.   return object
  2045. end
  2046.  
  2047. objects.Container.Window.create = function(windowName)
  2048.   local object = objects.Container.Window.new(windowName)
  2049.   windows.children[windowName] = object
  2050. end
  2051.  
  2052. objects.Container.Window.get = function(self, contentBuffer)
  2053.   return contentBuffer
  2054. end
  2055.  
  2056. objects.Container.Window.getContentArea = function(self)
  2057.   return 1, 1, maxX, maxY
  2058. end
  2059.  
  2060. -- >> Panel
  2061.  
  2062. objects.Container.Panel = {}
  2063. objects.Container.Panel.get = function(self, contentBuffer)
  2064.   log("objects.Container.Panel.get", "FUNC")
  2065.   local buffer = Buffer:new()
  2066.   buffer:init(self.width, self.height, self.path, objectColors.Container.Panel.border)
  2067.   buffer:addBuffer(2, 2, contentBuffer)
  2068.   buffer:addBuffer(self.width, 1, objects.Line.get("vertical", self.height, objectColors.Container.Panel.border, self.path))
  2069.   buffer:addBuffer(1, self.height, objects.Line.get("horizontal", self.width, objectColors.Container.Panel.border, self.path))
  2070.  
  2071.   return buffer
  2072. end
  2073.  
  2074. -- >> ScrollView
  2075.  
  2076. objects.Container.ScrollView = {}
  2077. objects.Container.ScrollView.new = function(self)
  2078.   self.scrollX = 0
  2079.   self.scrollY = 0
  2080.   self.maxScrollX = 0
  2081.   self.maxScrollY = 0
  2082.   self.scrollXEnabled = false
  2083.   self.scrollYEnabled = true
  2084. end
  2085.  
  2086. objects.Container.ScrollView.get = function(self, contentBuffer)
  2087.   log("objects.Container.ScrollView.get", "FUNC")
  2088.   log("ScrollView (ID: " .. tostring(self.objID) .. ")", "INFO")
  2089.  
  2090.   local buffer = Buffer:new()
  2091.   buffer:init(self.width, self.height, self.path, objectColors.Container.ScrollView.border)
  2092.   buffer:addBuffer(2 - self.scrollX, 2 - self.scrollY, contentBuffer)
  2093.   buffer:makeBorder(self.path, objectColors.Container.ScrollView.border)
  2094.  
  2095.   --local edgePixel = { background=objectColors.Container.ScrollView.border, path=self.path }
  2096.   --buffer:setPixel(self.width, 1, edgePixel)
  2097.   --buffer:setPixel(self.width, self.height, edgePixel)
  2098.   --buffer:setPixel(1, self.height, edgePixel)
  2099.  
  2100.   if (self.scrollXEnabled) then
  2101.     self.maxScrollX = contentBuffer.width - self.width + 1
  2102.    
  2103.     buffer:addBuffer(2, self.height, objects.Line.get("horizontal", self.width - 2, objectColors.Container.ScrollView.scrollBackground))
  2104.    
  2105.     if (self.width > 4) then
  2106.       local scrollBarInfo = getScrollBarInfo(self.scrollX, self.width, contentBuffer.width)
  2107.       buffer:addBuffer(scrollBarInfo.pos + 3, self.height, objects.Line.get("horizontal", scrollBarInfo.size, objectColors.Container.ScrollView.scrollForeground, self.path))
  2108.     end
  2109.    
  2110.     buffer:setPixel(2, self.height, { char="<", path=self.path })
  2111.     buffer:setPixel(self.width - 1, self.height, { char=">", path=self.path })
  2112.   else
  2113.     --buffer:addBuffer(2, self.height, objects.Line.get("horizontal", self.width - 2, objectColors.Container.ScrollView.border, self.path))
  2114.   end
  2115.  
  2116.   if (self.scrollYEnabled) then
  2117.     self.maxScrollY = contentBuffer.height - self.height + 1
  2118.     buffer:addBuffer(self.width, 2, objects.Line.get("vertical", self.height - 2, objectColors.Container.ScrollView.scrollBackground))
  2119.    
  2120.     if (self.height > 4) then
  2121.       local scrollBarInfo = getScrollBarInfo(self.scrollY, self.height, contentBuffer.height)
  2122.       buffer:addBuffer(self.width, scrollBarInfo.pos + 3, objects.Line.get("vertical", scrollBarInfo.size, objectColors.Container.ScrollView.scrollForeground, self.path))
  2123.     end
  2124.    
  2125.     buffer:setPixel(self.width, 2, { char="^", path=self.path })
  2126.     buffer:setPixel(self.width, self.height - 1, { char="V", path=self.path })
  2127.   else
  2128.     --buffer:addBuffer(self.width, 2, objects.Line.get("vertical", self.height - 2, objectColors.Container.ScrollView.border, self.path))
  2129.   end
  2130.  
  2131.   return buffer
  2132. end
  2133.  
  2134. objects.Container.ScrollView.click = function(self, x, y)
  2135.   if (x == self.width and y == 2) then -- Up
  2136.     log("Up")
  2137.     if (self.scrollY > 0) then
  2138.       self.scrollY = self.scrollY - 1
  2139.     end
  2140.   elseif (x == self.width and y == self.height - 1) then -- Down
  2141.     log("Down")
  2142.     if (self.scrollY < self.maxScrollY) then
  2143.       self.scrollY = self.scrollY + 1
  2144.     end
  2145.   elseif (x == 2 and y == self.height) then -- Left
  2146.     log("Left")
  2147.     if (self.scrollX > 0) then
  2148.       self.scrollX = self.scrollX - 1
  2149.     end
  2150.   elseif (x == self.width - 1 and y == self.height) then -- Right
  2151.     log("Right")
  2152.     if (self.scrollX < self.maxScrollX) then
  2153.       self.scrollX = self.scrollX + 1
  2154.     end
  2155.   end
  2156.  
  2157.   windowBuffer:addBuffer(self.absoluteX, self.absoluteY, objects.get(self))
  2158.   objects.draw(self)
  2159. end
  2160.  
  2161. objects.Container.ScrollView.getPosModifier = function(self, x, y)
  2162.   return self.scrollX * -1, self.scrollY * -1
  2163. end
  2164.  
  2165. objects.Container.ScrollView.addMarker = function(self, buffer)
  2166.  
  2167.   if not (self.scrollXEnabled and self.scrollYEnabled) then
  2168.     if (not self.scrollXEnabled and self.width > 2) then
  2169.       buffer:addBuffer(2, self.height, objects.Line.get("horizontal", self.width - 2, objectColors.Editor.editMarker))
  2170.     end
  2171.    
  2172.     if (not self.scrollYEnabled and self.height > 2) then
  2173.       buffer:addBuffer(self.width, 2, objects.Line.get("vertical", self.height - 2, objectColors.Editor.editMarker))
  2174.     end
  2175.   end
  2176. end
  2177.  
  2178. objects.Container.ScrollView.editorClick = function(self, x, y)
  2179.   -- Check whether an arrow has been clicked.
  2180.   if (self.scrollYEnabled and x == self.width and y == 2) then -- Up
  2181.     log("Up")
  2182.     if (self.scrollY > 0) then
  2183.       self.scrollY = self.scrollY - 1
  2184.     end
  2185.    
  2186.     return true
  2187.   elseif (self.scrollYEnabled and x == self.width and y == self.height - 1) then -- Down
  2188.     log("Down")
  2189.     self.scrollY = self.scrollY + 1
  2190.     return true
  2191.   elseif (self.scrollXEnabled and x == 2 and y == self.height) then -- Left
  2192.     log("Left")
  2193.     if (self.scrollX > 0) then
  2194.       self.scrollX = self.scrollX - 1
  2195.     end
  2196.    
  2197.     return true
  2198.   elseif (self.scrollXEnabled and x == self.width - 1 and y == self.height) then -- Right
  2199.     log("Right")
  2200.     self.scrollX = self.scrollX + 1
  2201.     return true
  2202.   else -- Check whether the scrollBar has been clicked.
  2203.     if (x >= 1 and x <= self.width - 1 and y == self.height) then
  2204.       self.scrollXEnabled = not self.scrollXEnabled
  2205.     elseif (x == self.width and y > 1 and y < self.height - 1) then
  2206.       self.scrollYEnabled = not self.scrollYEnabled
  2207.     end
  2208.   end
  2209.  
  2210.   return false
  2211. end
  2212.  
  2213. -- >>> Objects that can't be added by the user.
  2214.  
  2215. -- >> Line
  2216.  
  2217. objects.Line = {}
  2218. objects.Line.get = function(orientation, length, color, path)
  2219.   if (orientation ~= "horizontal" and orientation ~= "vertical") then
  2220.     orientation = getOrientation(orientation) or error("Orientation " .. orientation .. " is invalid!", 1)
  2221.   end
  2222.  
  2223.   assert(length)
  2224.   assert(color)
  2225.  
  2226.   local width, height
  2227.   if (orientation == "horizontal") then
  2228.     width, height = length, 1
  2229.   else
  2230.     width, height = 1, length
  2231.   end
  2232.  
  2233.   local buffer = Buffer:new()
  2234.   buffer:init(width, height, path, color)
  2235.  
  2236.   return buffer
  2237. end
  2238.  
  2239. -- >> Selector
  2240. objects.Selector = {}
  2241. objects.Selector.draw = function(x, y, elements)
  2242.   width = getLongestString(elements) + 2
  2243.   height = #elements + 2 -- Elements + up and down
  2244.   elementCount = #elements
  2245.   displayCount = elementCount
  2246.  
  2247.   enoughXSpace = true
  2248.   -- determine where the selector should actually be displayed
  2249.   if (width > maxX) then -- Not enough monitors horizontally?
  2250.     x = 1
  2251.     enoughXSpace = false
  2252.   elseif (maxX - x < width) then -- Not enough space to the right.
  2253.     if (x >= width) then -- Let's see if there is space to the left.
  2254.       x = x - width
  2255.     else -- No space? Check where you've got more space.
  2256.       if (maxX / 2) > x then -- More space to the left.
  2257.         x = maxX - width + 1
  2258.         enoughXSpace = false
  2259.       else -- More space to the right
  2260.         x = 1
  2261.         enoughXSpace = false
  2262.       end
  2263.     end
  2264.   else -- Enough space to the right.
  2265.     x = x + 1
  2266.   end
  2267.  
  2268.   if (height > maxY - y) then -- Not enough space from y to bottom.
  2269.     if ((maxY / 2) > y) then -- More space below y.
  2270.       if enoughXSpace then
  2271.         if (maxY < height) then -- Too big for the whole screen.
  2272.           y = 1
  2273.           displayCount = maxY - 2
  2274.         else -- Enough space next to x and not too high.
  2275.           y = maxY - height
  2276.         end
  2277.       else -- Can't display it next to the selected point.
  2278.         y = y + 1
  2279.         displayCount = maxY - y - 1
  2280.       end
  2281.     else -- More space above y.
  2282.       if enoughXSpace then
  2283.         if (y < height) then -- Not enough space from top to y.
  2284.           if (maxY < height) then -- Too big for the whole screen.
  2285.             y = 1
  2286.             displayCount = maxY - 2
  2287.           else -- Enough space next to x and not too high.
  2288.             y = 1
  2289.           end
  2290.         else -- Enough space from top to y.
  2291.           y = y - height + 1
  2292.         end
  2293.       else
  2294.         if (y < height) then -- Not enough space from top to y.
  2295.           if (maxY < height) then -- Too big for the whole screen.
  2296.             y = 1
  2297.             displayCount = maxY - 2
  2298.           else -- Not enough space next to x but not too high.
  2299.             y = 1
  2300.             displayCount = y - 4
  2301.           end
  2302.         else -- Enough space from top to y.
  2303.           y = y - height
  2304.         end
  2305.       end
  2306.     end
  2307.   end
  2308.  
  2309.   out.setBackgroundColor(objectColors.background)
  2310.  
  2311.   -- Read the user input.
  2312.   scroll = 1
  2313.   right = x + width - 1
  2314.   bottom = y + displayCount + 1
  2315.  
  2316.   finished = false
  2317.   while not finished do
  2318.     -- Display the actual selector.
  2319.     drawBox(x, y, width, height, objectColors.List.default)
  2320.    
  2321.     out.setBackgroundColor(objectColors["List"].default)
  2322.     middle = math.floor(width / 2)
  2323.     out.setCursorPos(x + middle, y)
  2324.     out.write("^")
  2325.     out.setCursorPos(x + middle, bottom)
  2326.     out.write("V")
  2327.    
  2328.     for i = 1, displayCount do
  2329.       out.setCursorPos(x, y + i)
  2330.       out.write(" " .. elements[i + scroll - 1] .. " ")
  2331.     end
  2332.     out.setBackgroundColor(objectColors.background)
  2333.    
  2334.     touchX, touchY, mouseButton = getCursorInput()
  2335.    
  2336.     if (touchX < x or touchX > right or touchY < y or touchY > bottom) then
  2337.       selectedItem = nil
  2338.       result = false
  2339.       finished = true
  2340.     else -- User touched the selector.
  2341.       if (touchY == y) then -- up
  2342.         if (scroll > 1) then -- Check if it makes sense to scroll up.
  2343.           scroll = scroll - 1
  2344.         end
  2345.       elseif (touchY == bottom) then -- down
  2346.         if (displayCount < elementCount) then
  2347.           if (scroll <= elementCount - displayCount) then
  2348.             scroll = scroll + 1
  2349.           end
  2350.         end
  2351.       else
  2352.         selectedItem = elements[touchY - y + scroll - 1]
  2353.         result = true
  2354.         finished = true
  2355.       end
  2356.     end
  2357.   end
  2358.  
  2359.   drawWindow(currentWindow)
  2360.   return result
  2361. end
  2362.  
  2363. -- >> API Functions
  2364.  
  2365. -- API function: Sets the value of all variables
  2366. -- with the given ID.
  2367. function setVariableValue(variableID, newVar)
  2368.   variableValues[variableID] = newVar
  2369. end
  2370.  
  2371. -- API function: Sets the value of all progressBars
  2372. -- with the given ID.
  2373. function setProgressBarValue(objID, newVar)
  2374.   progressBarValues[objID] = newVar
  2375. end
  2376.  
  2377. -- >> User Input Functions
  2378.  
  2379. -- Gets any input of the user
  2380. -- (not from the environment)
  2381. function getAnyInput()
  2382.   local finished = false
  2383.   local event = {}
  2384.  
  2385.   while not finished do
  2386.     finished = true
  2387.     os.sleep(0)
  2388.    
  2389.     input = {os.pullEvent()}
  2390.     event.eventType = input[1]
  2391.    
  2392.     if (event.eventType == "monitor_touch" and not outIsTerm) then
  2393.       event.eventType = "mouse"
  2394.       event.x = input[3]
  2395.       event.y = input[4]
  2396.       event.mouseButton = 1
  2397.     elseif (event.eventType == "mouse_click" and outIsTerm) then
  2398.       event.eventType = "mouse"
  2399.       event.x = input[3]
  2400.       event.y = input[4]
  2401.       event.mouseButton = input[2]
  2402.     elseif (event.eventType == "key") then
  2403.       event.key = input[2]
  2404.     else
  2405.       finished = false
  2406.     end
  2407.   end
  2408.  
  2409.   return event
  2410. end
  2411.  
  2412. -- Returns where the user clicked and which button
  2413. -- he pressed (always 1 if it's a monitor).
  2414. function getCursorInput()
  2415.   local finished = false
  2416.  
  2417.   while not finished do
  2418.     event, param, x, y = os.pullEvent()
  2419.    
  2420.     if (event == "monitor_touch" and not outIsTerm) then
  2421.       mouseButton = 1
  2422.       finished = true
  2423.     elseif (event == "mouse_click" and outIsTerm) then
  2424.       mouseButton = param
  2425.       finished = true
  2426.     end
  2427.   end
  2428.  
  2429.   return x, y, mouseButton
  2430. end
  2431.  
  2432. -- Waits until any key gets pressed.
  2433. function getKeyInput()
  2434.   os.pullEvent("key")
  2435. end
  2436.  
  2437. function readUserInput(message, isPassword)
  2438.   if not outIsTerm then
  2439.     print(message)
  2440.   end
  2441.    
  2442.   if isPassword  then
  2443.     ret = read("*")
  2444.   else
  2445.     ret = read()
  2446.   end
  2447.  
  2448.   return ret
  2449. end
  2450.  
  2451. -- >> Display Functions
  2452.  
  2453. -- Has to be used instead of paintutils.drawpixel
  2454. function drawPixel(x, y, color)
  2455.   out.setCursorPos(x, y)
  2456.   out.setBackgroundColor(color)
  2457.   out.write(" ")
  2458. end
  2459.  
  2460. function drawBox(x, y, width, height, color)
  2461.   out.setBackgroundColor(color)
  2462.  
  2463.   for row = 1, height do
  2464.     out.setCursorPos(x, y + row - 1)
  2465.     out.write(string.rep(" ", width))
  2466.    
  2467.     for col = x, width do
  2468.       if (windowBuffer[col] and windowBuffer[col][y + row - 1]) then
  2469.         windowBuffer[col][y + row - 1].draw = true
  2470.       end
  2471.     end
  2472.   end
  2473. end
  2474.  
  2475. -- Displays the text with red background colour.
  2476. function drawSimpleButton(x, y, text)
  2477.   out.setCursorPos(x, y)
  2478.   out.setBackgroundColor(objectColors.Button.default)
  2479.   out.write(text)
  2480.   out.setBackgroundColor(objectColors.background)
  2481. end
  2482.  
  2483. -- Displays the default buttons.
  2484. function drawDefaultButtons()
  2485.   local window = getCurrentWindow()
  2486.   local button
  2487.  
  2488.   if (window.showRefreshButton) then
  2489.     button = defaultButtons.refresh
  2490.     drawSimpleButton(button.left, button.top, button.text) -- Refresh
  2491.   end
  2492.  
  2493.   if (window.showBackButton and currentWindow ~= "mainWindow") then
  2494.     button = defaultButtons.back
  2495.     drawSimpleButton(button.left, button.top, button.text) -- Back
  2496.   end
  2497.  
  2498.   button = defaultButtons.quit
  2499.   drawSimpleButton(button.left, button.top, button.text) -- Quit
  2500.  
  2501.   button = defaultButtons.options
  2502.   if (button.required()) then
  2503.     drawSimpleButton(button.left, button.top, button.text) -- Options
  2504.   end
  2505. end
  2506.  
  2507. -- Loads the values of all variables and progressBars
  2508. -- of the current window.
  2509. function loadObjects()
  2510.   local window = getCurrentWindow()
  2511.   loadObjectsOf(window)
  2512. end
  2513.  
  2514. -- Loads all objects inside the container and its containers (recursive)
  2515. function loadObjectsOf(container)
  2516.   for _, object in pairs(container.children) do
  2517.     local objectType = object.objType
  2518.    
  2519.     if (objectType == "Variable" or objectType == "ProgressBar") then
  2520.       local x = object.absoluteX
  2521.       local y = object.absoluteY
  2522.       local value = nil
  2523.      
  2524.       if (objectType == "Variable") then
  2525.         value = getVariableValue(object)
  2526.       elseif (objectType == "ProgressBar") then
  2527.         value = getProgressBarValue(object)
  2528.       end
  2529.      
  2530.       objects.draw(object, value)
  2531.     elseif (object.isContainer) then
  2532.       loadObjectsOf(object)
  2533.     end
  2534.   end
  2535. end
  2536.  
  2537. -- Displays all objects of the window with the
  2538. -- ID windowID on the screen and changes the
  2539. -- variable "currentWindow".
  2540. function drawWindow(windowID)
  2541.   --clearScreen()
  2542.  
  2543.   if windowID then
  2544.     currentWindow = windowID
  2545.   else
  2546.     windowID = currentWindow
  2547.   end
  2548.  
  2549.   local windowObject = getCurrentWindow()
  2550.  
  2551.   windowBuffer = objects.Container.get(windowObject)
  2552.   windowBuffer:draw()
  2553.  
  2554.   if autoLoadObjects then
  2555.     loadObjects()
  2556.   end
  2557.  
  2558.   drawDefaultButtons()
  2559. end
  2560.  
  2561. -- >> Input Processing
  2562.  
  2563. function pullEvent(requestedEvent)
  2564.   if not eventTypeExists(requestedEvent) then
  2565.     clearScreen()
  2566.     print("Event type " .. tostring(requestedEvent) .. " is invalid!")
  2567.     print()
  2568.     print("Available event types:")
  2569.    
  2570.     for _, event in pairs(eventTypes) do
  2571.       print("  " .. event)
  2572.     end
  2573.    
  2574.     error()
  2575.   end
  2576.  
  2577.   local finished = false
  2578.   local event, params
  2579.  
  2580.   while not finished do
  2581.     event, params = getInput()
  2582.    
  2583.     if event then
  2584.       if (requestedEvent ~= nil) then
  2585.         if (requestedEvent == event or event == "quit") then
  2586.           finished = true
  2587.         end
  2588.       else
  2589.         finished = true
  2590.       end
  2591.     end
  2592.   end
  2593.  
  2594.   return event, unpack(params)
  2595. end
  2596.  
  2597. function getInput()
  2598.   log("getInput", "FUNC")
  2599.  
  2600.   local finished = false
  2601.   local event, params
  2602.   local x, y, mouseButton = getCursorInput()
  2603.   log("Received mouse input. X: " .. x .. ", Y: " .. y .. ", button: " .. mouseButton .. ".", "INFO")
  2604.  
  2605.   if (defaultButtonPressed("quit", x, y)) then
  2606.     log("Quit pressed")
  2607.     quit = true
  2608.   elseif (defaultButtonPressed("refresh", x, y)) then
  2609.     log("Refresh pressed")
  2610.     drawWindow()
  2611.     finished = true
  2612.   elseif (defaultButtonPressed("back", x, y)) then
  2613.     log("Back pressed")
  2614.     if (windows.children[currentWindow].parent ~= nil) then
  2615.       drawWindow(windows.children[currentWindow].parent)
  2616.       finished = true
  2617.     else
  2618.       drawWindow("mainWindow")
  2619.       finished = true
  2620.     end
  2621.   end
  2622.  
  2623.   if finished then
  2624.     return nil
  2625.   elseif quit then
  2626.     return "quit", { "Graffiti" } -- Used for the API
  2627.   end
  2628.  
  2629.   local param
  2630.   local path = windowBuffer.bufferTable[x][y].path
  2631.   log(path)
  2632.   if path and #path > 0 then
  2633.     local object = Path.getObject(path)
  2634.     local clickX, clickY = Path.getRelativePos(path, x, y)
  2635.     clickX, clickY = clickX - object.x + 1, clickY - object.y + 1
  2636.     log(object.objType .. " \"" .. object.objID .. "\" clicked at " .. clickX .. ", " .. clickY .. ".")
  2637.    
  2638.     event, params = objects.click(object, clickX, clickY)
  2639.   end
  2640.  
  2641.   return event, params
  2642. end
  2643.  
  2644. -- Shows the message on the computer for debugging.
  2645. function debugMessage(message)
  2646.   if outIsTerm then
  2647.     error("Can't display a debug message on a computer!")
  2648.   end
  2649.  
  2650.   print(message)
  2651. end
  2652.  
  2653. -- Calls the "getInput" function until the user presses the quit-button.
  2654. function main()
  2655.   drawWindow("mainWindow")
  2656.  
  2657.   while not quit do
  2658.     getInput()
  2659.   end
  2660. end
  2661.  
  2662. function splitAt(self, delimiter)
  2663.   delimiterPos = string.find(self, delimiter)
  2664.   left = string.sub(self, 1, delimiterPos - 1)
  2665.   right = string.sub(self, delimiterPos + #delimiter)
  2666.  
  2667.   return left, right
  2668. end
  2669.  
  2670. -- >>> Editor
  2671.  
  2672. function generateWindowList()
  2673.   local ret = {}
  2674.  
  2675.   for key, value in pairs(windows.children) do
  2676.     table.insert(ret, key)
  2677.   end
  2678.  
  2679.   return ret
  2680. end
  2681.  
  2682. editorWindows = {
  2683.   children = {
  2684.     mainWindow = {
  2685.       objType = "Window",
  2686.       children = {
  2687.         [1] = { objType="Text", objID="ModeText", x=2, y=1, text="Mode:", path={1} };
  2688.         [2] = { objType="List", x=2, y=3, elements=editActions, objID="editActionList", isMultiselect=false, canClick=true, path={2} };
  2689.         [3] = { objType="Button", objID="btnLastWindow", x=2, y=7, width=13, height=3, text="Last Window", funcType="function", param="editLastWindow", canClick=true, canScale=true, path={3} };
  2690.         [4] = { objType="Button", objID="btnWindowList", x=17, y=7, width=13, height=3, text="Window List", funcType="function", param="loadWindowList", canClick=true, canScale=true, path={4} };
  2691.       };
  2692.     };
  2693.    
  2694.     windowListWindow = {
  2695.       objType = "Window",
  2696.       children = {
  2697.         [1] = { objType="List", objID="WindowList", x=2, y=2, elements=windowList, objID="windowList", isMultiselect=false, canClick=true, path={1} };
  2698.         [2] = { objType="Button", objID="SetParent", x=2, y=maxY-6, width=12, height=1, text="Set parent", funcType="function", param="setParent", canClick=true, canScale=true, path={2} };
  2699.         [3] = { objType="Button", objID="NewWindow", x=2, y=maxY-4, width=8, height=1, text="New", funcType="function", param="newWindow", canClick=true, canScale=true, path={3} };
  2700.         [4] = { objType="Button", objID="EditWindow", x=2, y=maxY-3, width=8, height=1, text="Edit", funcType="function", param="editWindow", canClick=true, canScale=true, path={4} };
  2701.         [5] = { objType="Button", objID="DeleteWindow", x=2, y=maxY-2, width=8, height=1, text="Delete", funcType="function", param="deleteWindow", canClick=true, canScale=true, path={5} };
  2702.       };
  2703.     };
  2704.   };
  2705. }
  2706.  
  2707. -- Used to give a List-object an array of all windows
  2708. function editorFunctions.loadWindowList()
  2709.   windowList = generateWindowList()
  2710.   editorWindows.children.windowListWindow.children[1].elements = windowList
  2711.   changeButtonColor = false
  2712.   drawWindow("windowListWindow")
  2713. end
  2714.  
  2715. function editorFunctions.editLastWindow()
  2716.   if (lastWindow == nil) then
  2717.     lastWindow = "mainWindow"
  2718.   end
  2719.  
  2720.   showEditorOptions = false
  2721.   drawWindow(lastWindow)
  2722.   changeButtonColor = false
  2723. end
  2724.  
  2725. -- Let's the user define the parent-attribute of the current window.
  2726. function editorFunctions.setParent()
  2727.   if (selectedItems.windowList == nil) then
  2728.     return
  2729.   end
  2730.  
  2731.   local list = editorWindows.children.windowListWindow.children[1]
  2732.   local selected = objects.List.getFirstSelectedKey(list)
  2733.  
  2734.   for i = 1, list.height do
  2735.     if (i ~= selected) then
  2736.       if (windowList[i] == windows.children[windowList[selected]].parent) then
  2737.         drawPixel(1, i + 1, colors.yellow)
  2738.       else
  2739.         drawPixel(1, i + 1, colors.lime)
  2740.       end
  2741.     end
  2742.   end
  2743.  
  2744.   local x, y, mouseButton = getCursorInput()
  2745.   local selectedParent = y - list.y + 1
  2746.  
  2747.   if (selectedParent >= 1 and selectedParent <= list.height) then -- Clicked inside the list.
  2748.     if (selectedParent ~= selected) then -- Selected parentWindow is not selected window.
  2749.       windows.children[windowList[selected]].parent = windowList[selectedParent]
  2750.     end
  2751.   end
  2752.  
  2753.   for i = 1, list.height do
  2754.     drawPixel(1, i + list.y - 1, colors.black)
  2755.   end
  2756. end
  2757.  
  2758. -- Creates a new window. The user has to enter the window name in the computer.
  2759. function editorFunctions.newWindow()
  2760.   clearScreen()
  2761.  
  2762.   if not outIsTerm then
  2763.     out.setCursorPos(2, 2)
  2764.     out.write("Enter a window-name.")
  2765.   end
  2766.  
  2767.   out.setCursorPos(1, 1)
  2768.  
  2769.   message = "Pleas enter the name of the new window or nothing to cancel."
  2770.   userInput = readUserInput(message, false)
  2771.  
  2772.   while (userInput ~= nil and userInput ~= "" and windows.children[userInput] ~= nil) do
  2773.     message = "There is already a window with that name!"
  2774.     userInput = readUserInput(message, false)
  2775.   end
  2776.  
  2777.   if (userInput ~= nil and userInput ~= "") then
  2778.     objects.Container.Window.create(userInput)
  2779.     showEditorOptions = false
  2780.     drawWindow(userInput)
  2781.     lastWindow = userInput
  2782.     changeButtonColor = false
  2783.   end
  2784. end
  2785.  
  2786. -- Edits the window that has been selected in the "windowList"-list.
  2787. function editorFunctions.editWindow()
  2788.   if selectedItems.windowList then
  2789.     local list = editorWindows.children.windowListWindow.children[1]
  2790.     local key = objects.List.getFirstSelectedKey(list)
  2791.     key = key or 1
  2792.    
  2793.     showEditorOptions = false
  2794.     lastWindow = windowList[key]
  2795.     drawWindow(lastWindow)
  2796.     changeButtonColor = false
  2797.   end
  2798. end
  2799.  
  2800. -- Deletes the window that has been selected in the "windowList"-list.
  2801. function editorFunctions.deleteWindow()
  2802.   local list = editorWindows.children.windowListWindow.children[1]
  2803.   local key = objects.List.getFirstSelectedKey(list)
  2804.  
  2805.   if (key and windowList[key] ~= "mainWindow") then
  2806.     windows.children[windowList[key]] = nil
  2807.     showEditorOptions = true
  2808.     editorFunctions.loadWindowList()
  2809.   end
  2810. end
  2811.  
  2812. -- Shows lines marking the top left part of an
  2813. -- object as well as well as pixels displaying
  2814. -- the alignment of an object.
  2815. function drawAlignmentLines(object, left, top, right, bottom)
  2816.   local color = objectColors.Editor.marker
  2817.   local moveX, moveY = objects.getMovePos(object)
  2818.  
  2819.   -- Draw the lines.
  2820.   objects["Line"].draw(left - 1, moveY, "left", left - 2, color) -- left
  2821.   objects["Line"].draw(moveX, top -1, "up", top - 2, color) -- up
  2822.   objects["Line"].draw(right + 1, moveY, "right", maxX - (right + 1), color) -- right
  2823.   objects["Line"].draw(moveX, bottom + 1, "down", maxY - (bottom + 1), color) -- down
  2824.  
  2825.   -- Display the alignment-pixels.
  2826.   horizontalAlignment = object.horizontalAlignment
  2827.   verticalAlignment = object.verticalAlignment
  2828.  
  2829.   if (horizontalAlignment == "left" or horizontalAlignment == "stretch") then -- left
  2830.     drawPixel(1, moveY, objectColors["Editor"].alignmentTrue)
  2831.   else
  2832.     drawPixel(1, moveY, objectColors["Editor"].alignmentFalse)
  2833.   end
  2834.  
  2835.   if (horizontalAlignment == "right" or horizontalAlignment == "stretch") then -- right
  2836.     drawPixel(maxX, moveY, objectColors["Editor"].alignmentTrue)
  2837.   else
  2838.     drawPixel(maxX, moveY, objectColors["Editor"].alignmentFalse)
  2839.   end
  2840.  
  2841.   if (verticalAlignment == "top" or verticalAlignment == "stretch") then -- top
  2842.     drawPixel(moveX, 1, objectColors["Editor"].alignmentTrue)
  2843.   else
  2844.     drawPixel(moveX, 1, objectColors["Editor"].alignmentFalse)
  2845.   end
  2846.  
  2847.   if (verticalAlignment == "bottom" or verticalAlignment == "stretch") then -- bottom
  2848.     drawPixel(moveX, maxY, objectColors["Editor"].alignmentTrue)
  2849.   else
  2850.     drawPixel(moveX, maxY, objectColors["Editor"].alignmentFalse)
  2851.   end
  2852.  
  2853.   out.setBackgroundColor(objectColors.background)
  2854. end
  2855.  
  2856. -- Returns the values of horizontalAlignment and
  2857. -- verticalAlignment depending which sides are set
  2858. -- to true.
  2859. function getAlignment(left, top, right, bottom)
  2860.   local retHorizontal, retVertical = "left", "top"
  2861.  
  2862.   if right then
  2863.     if left then
  2864.       retHorizontal = "stretch"
  2865.     else
  2866.       retHorizontal = "right"
  2867.     end
  2868.   else
  2869.     retHorizontal = "left"
  2870.   end
  2871.  
  2872.   if bottom then
  2873.     if top then
  2874.       retVertical = "stretch"
  2875.     else
  2876.       retVertical = "bottom"
  2877.     end
  2878.   else
  2879.     retVertical = "top"
  2880.   end
  2881.  
  2882.   return retHorizontal, retVertical
  2883. end
  2884.  
  2885. -- Let's the user delete an object or change its attributes depending on the current edit-mode.
  2886. function editObject(object)
  2887.   assert(object)
  2888.   log("editObject", "FUNC")
  2889.   log("Type: " .. object.objType .. ", ID" .. object.objID, "INFO")
  2890.  
  2891.   local objType = object.objType
  2892.   local modX, modY = objects.getPosModifier(object)
  2893.   local left, top, right, bottom = objects.getDimensions(object)
  2894.   left, top, right, bottom = left + modX, top + modY, right + modX, bottom + modY
  2895.   local actionsList = editorWindows.children.mainWindow.children[2]
  2896.   local action = editActions[objects.List.getFirstSelectedKey(actionsList)]
  2897.  
  2898.   if (action == "Delete") then
  2899.     objects.remove(object)
  2900.   elseif (action == "Attributes") then
  2901.     local objAttr = {  }
  2902.     local includedAttributes = {
  2903.       text = true,
  2904.       param = true,
  2905.       objID = true,
  2906.       message = true,
  2907.       elements = true,
  2908.       message = true,
  2909.       funcType = true,
  2910.       isPassword = true,
  2911.       isMultiselect = true,
  2912.       scrollXEnabled = true,
  2913.       scrollYEnabled = true,
  2914.     }
  2915.    
  2916.     index = 1
  2917.     for key, value in pairs(object) do
  2918.       if (includedAttributes[key]) then
  2919.         table.insert(objAttr, index, key)
  2920.         index = index + 1
  2921.       end
  2922.     end
  2923.    
  2924.     out.clear()
  2925.    
  2926.     local yPos = 2
  2927.     top = yPos
  2928.     for attrKey, attrValue in ipairs(objAttr) do
  2929.       out.setCursorPos(2, yPos)
  2930.       out.write(attrValue .. ": ")
  2931.       out.write(object[attrValue])
  2932.       yPos = yPos + 1
  2933.     end
  2934.     out.setCursorPos(2, yPos + 1)
  2935.     out.setBackgroundColor(colors.red)
  2936.     out.write(text.done)
  2937.     out.setBackgroundColor(objectColors.background)
  2938.    
  2939.     bottom = yPos - 1
  2940.     finished = false
  2941.     while not finished do
  2942.       local x, y, mouseButton = getCursorInput()
  2943.      
  2944.       if y >= top and y <= bottom then
  2945.         local selectedAttr = objAttr[y - 1]
  2946.         if not outIsTerm then
  2947.           drawPixel(1, y, colors.yellow)
  2948.         end
  2949.        
  2950.         if (selectedAttr == "param" or
  2951.             selectedAttr == "objID" or
  2952.             selectedAttr == "message" or
  2953.             selectedAttr == "elements") then
  2954.          
  2955.           if outIsTerm then
  2956.             out.setCursorPos(1, y)
  2957.             out.clearLine(y)
  2958.             out.setCursorPos(2, y)
  2959.             out.write(selectedAttr .. ": ")
  2960.           end
  2961.          
  2962.           userInput = readUserInput("Please enter a value for the " .. selectedAttr .. ".", false)
  2963.           if (userInput ~= nil) then
  2964.             object[selectedAttr] = userInput
  2965.           end
  2966.         elseif (selectedAttr == "text") then
  2967.           if outIsTerm then
  2968.             out.setCursorPos(1, y)
  2969.             out.clearLine(y)
  2970.             out.setCursorPos(2, y)
  2971.             out.write(selectedAttr .. ": ")
  2972.           end
  2973.          
  2974.           userInput = readUserInput("Please enter a value for the " .. selectedAttr .. ".", false)
  2975.           if (userInput ~= nil) then
  2976.             object[selectedAttr] = userInput
  2977.            
  2978.             if (object.objType == "Text") then
  2979.               object.width = #userInput
  2980.             end
  2981.           end
  2982.         elseif (selectedAttr == "funcType") then -- Button attribute
  2983.           if (object.funcType == "switch") then
  2984.             object[selectedAttr] = "function"
  2985.           elseif (object.funcType == "function") then
  2986.             object[selectedAttr] = "toggle function"
  2987.           else
  2988.             object[selectedAttr] = "switch"
  2989.           end
  2990.         elseif (selectedAttr == "isPassword" or
  2991.                 selectedAttr == "isMultiselect" or
  2992.                 selectedAttr == "toggle" or
  2993.                 selectedAttr == "scrollXEnabled" or
  2994.                 selectedAttr == "scrollYEnabled") then
  2995.           object[selectedAttr] = not object[selectedAttr]
  2996.         end
  2997.         drawPixel(1, y, colors.black)
  2998.         if (not finished and selectedAttr ~= nil) then
  2999.           out.setCursorPos(2, y) -- I don't know if that's neccessary...
  3000.           for i = 2, maxX do
  3001.             out.write(" ")
  3002.           end
  3003.           out.setCursorPos(2, y)
  3004.           out.write(selectedAttr .. ": ")
  3005.           out.write(object[selectedAttr])
  3006.         end
  3007.       elseif (y == yPos + 1 and x >= 2 and x <= 1 + string.len(text.done)) then
  3008.         finished = true
  3009.       end
  3010.     end
  3011.   else -- Design mode
  3012.     local moveX, moveY = objects.getMovePos(object)
  3013.     local scaleX, scaleY
  3014.    
  3015.     objects.draw(object, nil, true) -- Draw the object with its markers.
  3016.    
  3017.     if object.canScale then
  3018.       scaleX, scaleY = objects.getScalePos(object)
  3019.       drawPixel(scaleX, scaleY, objectColors.Editor.scale)
  3020.     end
  3021.    
  3022.     drawPixel(moveX, moveY, objectColors.Editor.move)
  3023.     out.setBackgroundColor(objectColors.background)
  3024.    
  3025.     local x, y, mouseButton = getCursorInput()
  3026.     local relX, relY = Path.getRelativePos(object.path, x, y)
  3027.    
  3028.     if (relX >= left and relX <= right and relY >= top and relY <= bottom) then -- clicked inside the object
  3029.       if (x == moveX and y == moveY) then -- move object
  3030.         drawPixel(moveX, moveY, objectColors.Editor.active)
  3031.         x, y, mouseButton = getCursorInput()
  3032.         addX = x - moveX
  3033.         addY = y - moveY
  3034.         objects.move(object, addX, addY)
  3035.       elseif (object.canScale and x == scaleX and y == scaleY) then -- scale object
  3036.         drawPixel(scaleX, scaleY, objectColors.Editor.active)
  3037.         out.setBackgroundColor(objectColors.background)
  3038.         local x, y, mouseButton = getCursorInput()
  3039.         relX, relY = Path.getRelativePos(object.path, x, y)
  3040.        
  3041.         objects.scale(object, relX + (modX * -1), relY + (modY * -1))
  3042.       else
  3043.         relX, relY = relX - object.x + 1, relY - object.y + 1
  3044.         objects.editorClick(object, relX + modX, relY + modY)
  3045.       end
  3046.     end
  3047.   end
  3048.  
  3049.   out.setBackgroundColor(objectColors.background)
  3050.   drawWindow(currentWindow)
  3051. end
  3052.  
  3053. function markVariables(container)
  3054.   assert(container)
  3055.  
  3056.   for _, object in pairs(container.children) do
  3057.     if (object.isContainer) then
  3058.       markVariables(object)
  3059.     elseif (object.objType == "Variable") then
  3060.       drawPixel(object.absoluteX, object.absoluteY, objectColors.Editor.marker)
  3061.       out.setBackgroundColor(objectColors.background)
  3062.     end
  3063.   end
  3064. end
  3065.  
  3066. function markDefaultButtons()
  3067.   local window = getCurrentWindow()
  3068.  
  3069.   -- refresh button
  3070.   local refresh = defaultButtons.refresh
  3071.   out.setCursorPos(refresh.left, refresh.top)
  3072.   if (window.showRefreshButton) then
  3073.     out.setBackgroundColor(objectColors.Button.default)
  3074.     out.write(refresh.text)
  3075.   else
  3076.     out.setBackgroundColor(objectColors.Editor.marker)
  3077.     out.write(string.rep(" ", #refresh.text))
  3078.   end
  3079.  
  3080.   -- back button
  3081.   if (currentWindow ~= "mainWindow") then
  3082.     local back = defaultButtons.back
  3083.     out.setCursorPos(back.left, back.top)
  3084.     if (window.showBackButton) then
  3085.       out.setBackgroundColor(objectColors.Button.default)
  3086.       out.write(back.text)
  3087.     else
  3088.       out.setBackgroundColor(objectColors.Editor.marker)
  3089.       out.write(string.rep(" ", #back.text))
  3090.     end
  3091.   end
  3092.  
  3093.   out.setBackgroundColor(objectColors.background)
  3094. end
  3095.  
  3096. function getEditorInput()
  3097.   log("getEditorInput", "FUNC")
  3098.  
  3099.   local event
  3100.   local x, y, mouseButton
  3101.  
  3102.   if not showEditorOptions then
  3103.     markVariables(getCurrentWindow())
  3104.     markDefaultButtons()
  3105.     event = getAnyInput()
  3106.    
  3107.     if (event.eventType == "mouse") then
  3108.       x, y, mouseButton = event.x, event.y, event.mouseButton
  3109.       log("Received mouse input. X: " .. x .. ", Y: " .. y .. ", button: " .. mouseButton .. ".")
  3110.     end
  3111.   end
  3112.  
  3113.   if (not showEditorOptions and event.eventType == "key") then
  3114.     callShortcut(event.key)
  3115.   elseif (showEditorOptions or defaultButtonPressed("options", x, y)) then
  3116.     showEditorOptions = true
  3117.     drawWindow("mainWindow")
  3118.     while showEditorOptions and not quit do
  3119.       getInput()
  3120.     end
  3121.   elseif (defaultButtonPressed("quit", x, y)) then
  3122.     quit = true
  3123.   elseif (defaultButtonPressed("refresh", x, y)) then
  3124.     windows.children[currentWindow].showRefreshButton = not windows.children[currentWindow].showRefreshButton
  3125.   elseif (defaultButtonPressed("back", x, y)) then
  3126.     windows.children[currentWindow].showBackButton = not windows.children[currentWindow].showBackButton
  3127.   else
  3128.     local container = getCurrentWindow()
  3129.    
  3130.     --log("Buffer table width: " .. #windowBuffer.bufferTable, "DEBUG")
  3131.     --log("Buffer table height: " .. #windowBuffer.bufferTable[1], "DEBUG")
  3132.     local path = windowBuffer.bufferTable[x][y].path
  3133.    
  3134.     if (path == nil or #path == 0) then -- No object touched. Draw selector for new object.
  3135.       drawPixel(x, y, objectColors.Editor.new)
  3136.       if (objects.Selector.draw(x, y, objectTypes)) then -- something has been selected
  3137.         objects.create(selectedItem, x, y)
  3138.       end
  3139.     else
  3140.       local object = Path.getObject(path)
  3141.      
  3142.       if (mouseButton == 1) then
  3143.         editObject(object)
  3144.       else
  3145.         if (objects.Selector.draw(x, y, rightClickActions)) then
  3146.           if (selectedItem == "Attributes") then
  3147.             lastItem = selectedItems.editActionList
  3148.             selectedItems.editActionList = { [2] = true }
  3149.             editObject(object)
  3150.             selectedItems.editActionList = lastItem
  3151.           elseif (selectedItem == "Delete") then
  3152.             objects.remove(object)
  3153.             drawWindow()
  3154.           end
  3155.         end
  3156.       end
  3157.     end
  3158.   end
  3159. end
  3160.  
  3161. -- Runs Graffiti in editMode.
  3162. function windowEditor()
  3163.   editMode = true
  3164.   autoLoadObjects = false
  3165.  
  3166.   showEditorOptions = true
  3167.  
  3168.   while not quit do
  3169.     getEditorInput()
  3170.   end
  3171. end
  3172.  
  3173. -- >>> Screen size adaption
  3174.  
  3175. function round(number)
  3176.   assert(number)
  3177.   comma = number % 1
  3178.   if comma < 0.5 then
  3179.     ret = math.floor(number)
  3180.   else
  3181.     ret = math.ceil(number)
  3182.   end
  3183.  
  3184.   return ret
  3185. end
  3186.  
  3187. function printInfo()
  3188.   print()
  3189.   print(version)
  3190.   print("Author: Encreedem")
  3191.   print()
  3192.   print("Param(s):")
  3193.   print("info - Shows some info about the program... but I guess you know that already.")
  3194.   print("edit - Starts the program in edit-mode.")
  3195.   print()
  3196.   print("Visit the CC-forums or my YouTube channel (Encreedem CP) for news and help.")
  3197. end
  3198.  
  3199. -- Gets called when Graffiti gets the argument "test"
  3200. function testMethod()
  3201.   error("Nothing to test...", 2)
  3202. end
  3203.  
  3204. -- >>> initialization
  3205.  
  3206. -- Runs the setup.
  3207. function runSetup()
  3208.   dataFolderPath = fs.combine(root, "GraffitiData")
  3209.   fs.makeDir(dataFolderPath)
  3210.   -- TODO: Make an actual setup.
  3211. end
  3212.  
  3213. -- Initializes the default buttons.
  3214. -- (Quit, Back, Refresh, Options)
  3215. function initDefaultButtons()
  3216.   defaultButtons.quit = {
  3217.     text=text.quit,
  3218.     left=maxX - string.len(text.quit) + 1,
  3219.     top=1,
  3220.     right=maxX,
  3221.     bottom=1,
  3222.     required = function()
  3223.       return true
  3224.     end
  3225.   }
  3226.  
  3227.   defaultButtons.back = {
  3228.     text = text.back,
  3229.     left = 1,
  3230.     top = 1,
  3231.     right = string.len(text.back),
  3232.     bottom = 1,
  3233.     required = function()
  3234.       return getCurrentWindow().showBackButton
  3235.     end
  3236.   }
  3237.  
  3238.   defaultButtons.refresh = {
  3239.     text = text.refresh,
  3240.     left = maxX - string.len(text.refresh) + 1,
  3241.     top = maxY,
  3242.     right = maxX,
  3243.     bottom = maxY,
  3244.     required = function()
  3245.       return (getCurrentWindow().showRefreshButton or (editMode and not showEdtorOptions))
  3246.     end
  3247.   }
  3248.  
  3249.   defaultButtons.options = {
  3250.     text = text.options,
  3251.     left = 1,
  3252.     top = maxY,
  3253.     right = string.len(text.options),
  3254.     bottom = maxY,
  3255.     required=function()
  3256.       return (editMode and not showEditorOptions)
  3257.     end
  3258.   }
  3259. end
  3260.  
  3261. function initWindows()
  3262.   for _, window in pairs(windows.children) do
  3263.     window.width, window.height = maxX, maxY
  3264.   end
  3265. end
  3266.  
  3267. -- Tells the user that the monitor or computer
  3268. -- doesn't support colors.
  3269. function showColorWarning()
  3270.   out.clear()
  3271.   out.setCursorPos(2, 2)
  3272.   out.write("This computer/monitor does not support colors!")
  3273.  
  3274.   local state = 0
  3275.   local move = "I don't know this move!"
  3276.   local finished = false
  3277.   while not finished and not quit do
  3278.     out.setCursorPos(1, 4)
  3279.     out.clearLine()
  3280.     out.setCursorPos(2, 4)
  3281.    
  3282.     if (state == 0) then
  3283.       move = "<( \" <) <( \" <) <( \" <)"
  3284.     elseif (state == 1 or state == 3 or state == 5) then
  3285.       move = "  (^\"^)   (^\"^)   (^\"^)"
  3286.     elseif (state == 2) then
  3287.       move = "  (> \" )> (> \" )> (> \" )>"
  3288.     elseif (state == 4) then
  3289.       move = " (> \" )><( \" )><( \" <)"
  3290.     elseif (state == 6) then
  3291.       move = "<( \" <) (>\"<) (> \" )>"
  3292.     elseif (state == 7) then
  3293.       move = " (v''v) (v''v) (v''v)"
  3294.     else
  3295.       error("Unable to show you that you need an advanced computer/monitor in a fancy way!")
  3296.     end
  3297.    
  3298.     out.write(move)
  3299.     state = (state + 1) % 8
  3300.     os.sleep(0.25)
  3301.   end
  3302. end
  3303.  
  3304. -- Checks if the monitor on monitorSide exists and wraps it into "monitor".
  3305. function getOutput()
  3306.   if (monitor == nil and outIsTerm == false) then
  3307.     local monitorFound = false
  3308.     for _, side in pairs(sides) do
  3309.       if (peripheral.getType(side) == "monitor") then
  3310.         monitor = peripheral.wrap(side)
  3311.         monitorFound = true
  3312.         out = monitor
  3313.         outIsTerm = false
  3314.       end
  3315.     end
  3316.    
  3317.     if not monitorFound then
  3318.       out = term
  3319.       outIsTerm = true
  3320.     end
  3321.   elseif outIsTerm then
  3322.     out = term
  3323.   else
  3324.     out = monitor
  3325.   end
  3326. end
  3327.  
  3328. function init()
  3329.   getOutput()
  3330.  
  3331.   maxX, maxY = out.getSize()
  3332.   if (maxX < 16 or maxY < 10) then -- smaller than 2x2
  3333.     print("Screen too small! You need at least 2x2 monitors!")
  3334.     return false
  3335.   elseif not out.isColor() then
  3336.     parallel.waitForAny(showColorWarning, getKeyInput)
  3337.     out.clear()
  3338.     out.setCursorPos(1, 1)
  3339.     return false
  3340.   end
  3341.  
  3342.   isAPI = (shell == nil)
  3343.  
  3344.   initDone = true
  3345.   return true
  3346. end
  3347.  
  3348. function checkArgs()
  3349.   doCall = main
  3350.   arg = args[1]
  3351.  
  3352.   if (arg ~= nil) then
  3353.     if (arg == "edit") then
  3354.       doCall = windowEditor
  3355.     elseif (arg == "info") then
  3356.       doCall = printInfo
  3357.     elseif (arg == "term") then
  3358.       outIsTerm = true
  3359.     elseif (arg == "test") then
  3360.       doCall = testMethod
  3361.     end
  3362.   end
  3363.  
  3364.   doCall()
  3365. end
  3366.  
  3367. if init() then
  3368.   Files.init()
  3369.   log("Graffiti initialized.")
  3370.   initWindows()
  3371.   initDefaultButtons()
  3372.   Files.clear(nil, Files.Settings.name)
  3373.  
  3374.   if not isAPI then
  3375.     checkArgs()
  3376.    
  3377.     -- Closing Program
  3378.     if editMode and saveAfterQuit then
  3379.       Files.save()
  3380.     end
  3381.    
  3382.     out.setTextColor(colors.white)
  3383.     out.setBackgroundColor(colors.black)
  3384.     out.clear()
  3385.     out.setCursorPos(1, 1)
  3386.   end
  3387. else
  3388.   error("Graffiti Initialization failed!")
  3389. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement