Advertisement
Encreedem

Graffiti v1.7.2

May 6th, 2014
1,944
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 133.32 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: Fix this until the 1.7 update!
  5. -- Well... at least I tried.
  6. -- TODO: Bug: ScrollView doesn't get updated immediately when its content changes.
  7. -- TODO: Clean up all the code below this comment.
  8.  
  9. local version = "1.7.2"
  10.  
  11. -- Used to save temporary data of objects.
  12. ObjectData = {
  13.   Input = {};
  14.   List = {};
  15.   FileSelector = {};
  16.   Button = {};
  17.   CheckBox = {};
  18.   RadioButton = {};
  19.   Slider = {};
  20.   DropDownList = {};
  21. }
  22.  
  23. UserData = {}
  24.  
  25. --Monitor
  26. local monitor = nil
  27.  
  28. -- Colors
  29. local ObjectColors = {}
  30.  
  31. -- Color Themes
  32. local ColorThemes = {}
  33. ColorThemes.Default = { -- Used colors: As many as I could get. (black background)
  34.   background = colors.black;
  35.   text = colors.white;
  36.   disabled = colors.lightGray;
  37.   disabledText = colors.black;
  38.   Button = { default = colors.red; active = colors.lime; text = colors.white };
  39.   ProgressBar = { low = colors.red; medium = colors.yellow; high = colors.lime; background = colors.gray; };
  40.   Input = { default = colors.gray; text = colors.white; active = colors.lightBlue; };
  41.   List = { default = colors.gray; active = colors.lightBlue; };
  42.   FileSelector = { default = colors.red; text = colors.white; dir = colors.lime; file = colors.white; active = colors.lime; };
  43.   CheckBox = { default = colors.white; active = colors.green; };
  44.   RadioButton = { default = colors.white; active = colors.lime };
  45.   Slider = { default = colors.orange; text = colors.black; active = colors.white; activeText = colors.black };
  46.   DropDownList = { default = colors.gray; text = colors.white; active = colors.lightGray; };
  47.   DefaultButtons = {
  48.     default = colors.red;
  49.     text = colors.white;
  50.     Quit = {
  51.       default = colors.red;
  52.       text = colors.white;
  53.     };
  54.   };
  55.   Editor = {
  56.     new = colors.white;
  57.     active = colors.lime;
  58.     move = colors.magenta;
  59.     scale = colors.pink;
  60.     marker = colors.gray;
  61.     textInputMarker = colors.yellow;
  62.     editMarker = colors.lime;
  63.     alignmentTrue = colors.lime;
  64.     alignmentFalse = colors.red;
  65.     selector1 = colors.gray;
  66.     selector2 = colors.lightGray;
  67.     selectorText = colors.white;
  68.   };
  69.   Container = {
  70.     Panel = {
  71.       border = colors.white;
  72.     };
  73.     ScrollView = {
  74.       border = colors.white;
  75.       scrollBackground = colors.lightGray;
  76.       scrollForeground = colors.gray;
  77.     };
  78.   };
  79. }
  80.  
  81. ColorThemes["Windows CC"] = { -- Used colors: none, just white, lightGray, gray and black... (white background)
  82.   background = colors.white;
  83.   text = colors.black;
  84.   disabled = colors.gray;
  85.   disabledText = colors.white;
  86.   Button = { default = colors.lightGray; active = colors.lightBlue; text = colors.black };
  87.   ProgressBar = { low = colors.lime; medium = colors.lime; high = colors.lime; background = colors.lightGray; };
  88.   Input = { default = colors.lightGray; text = colors.black; active = colors.white; };
  89.   List = { default = colors.lightGray; active = colors.lightBlue; };
  90.   FileSelector = { default = colors.gray; text = colors.black; dir = colors.blue; file = colors.black; active = colors.lightBlue; };
  91.   CheckBox = { default = colors.gray; active = colors.lightBlue; };
  92.   RadioButton = { default = colors.gray; active = colors.lightBlue; };
  93.   Slider = { default = colors.gray; text = colors.black; active = colors.white; activeText = colors.black };
  94.   DropDownList = { default = colors.gray; text = colors.white; active = colors.lightBlue; };
  95.   DefaultButtons = {
  96.     default = colors.lightGray;
  97.     text = colors.black;
  98.     Quit = {
  99.       default = colors.red;
  100.       text = colors.white;
  101.     };
  102.   };
  103.   Editor = {
  104.     new = colors.lightBlue;
  105.     active = colors.black;
  106.     move = colors.magenta;
  107.     scale = colors.pink;
  108.     marker = colors.gray;
  109.     textInputMarker = colors.black;
  110.     editMarker = colors.lightBlue;
  111.     alignmentTrue = colors.lime;
  112.     alignmentFalse = colors.red;
  113.     selector1 = colors.gray;
  114.     selector2 = colors.lightGray;
  115.     selectorText = colors.white;
  116.   };
  117.   Container = {
  118.     Panel = {
  119.       border = colors.lightGray;
  120.     };
  121.     ScrollView = {
  122.       border = colors.lightGray;
  123.       scrollBackground = colors.lightGray;
  124.       scrollForeground = colors.gray;
  125.     };
  126.   };
  127. }
  128.  
  129. ColorThemes.Fire = { -- Used colors: red, orange, yellow, lightGray, gray background
  130.   background = colors.gray;
  131.   text = colors.black;
  132.   disabled = colors.black;
  133.   disabledText = colors.white;
  134.   Button = { default = colors.orange; active = colors.red; text = colors.black };
  135.   ProgressBar = { low = colors.yellow; medium = colors.orange; high = colors.red; background = colors.lightGray; };
  136.   Input = { default = colors.yellow; text = colors.black; active = colors.orange; };
  137.   List = { default = colors.orange; active = colors.red; };
  138.   FileSelector = { default = colors.orange; text = colors.black; dir = colors.lime; file = colors.white; active = colors.lime; };
  139.   CheckBox = { default = colors.white; active = colors.red; };
  140.   RadioButton = { default = colors.white; active = colors.red };
  141.   Slider = { default = colors.orange; text = colors.black; active = colors.red; activeText = colors.black };
  142.   DropDownList = { default = colors.orange; text = colors.black; active = colors.red; };
  143.   DefaultButtons = {
  144.     default = colors.orange;
  145.     text = colors.black;
  146.     Quit = {
  147.       default = colors.red;
  148.       text = colors.white;
  149.     };
  150.   };
  151.   Editor = {
  152.     new = colors.lightBlue;
  153.     active = colors.white;
  154.     move = colors.blue;
  155.     scale = colors.lightBlue;
  156.     marker = colors.lightGray;
  157.     textInputMarker = colors.orange;
  158.     editMarker = colors.red;
  159.     alignmentTrue = colors.lime;
  160.     alignmentFalse = colors.red;
  161.     selector1 = colors.gray;
  162.     selector2 = colors.lightGray;
  163.     selectorText = colors.white;
  164.   };
  165.   Container = {
  166.     Panel = {
  167.       border = colors.lightGray;
  168.     };
  169.     ScrollView = {
  170.       border = colors.lightGray;
  171.       scrollBackground = colors.lightGray;
  172.       scrollForeground = colors.yellow;
  173.     };
  174.   };
  175. }
  176.  
  177. ObjectColors = ColorThemes.Default
  178.  
  179. -- Languages
  180. local Languages = {
  181.   ["en-US"] = {
  182.     back = " < ";
  183.     cancel = "Cancel";
  184.     colorTheme = "Color Theme";
  185.     done = "Done";
  186.     editEntries = "Edit Entries";
  187.     finish = "Finish";
  188.     finished = "Done";
  189.     language = "Language";
  190.     lastWindow = "Last Window";
  191.     moveDown = "Move Down";
  192.     moveUp = "Move Up";
  193.     newEntry = "New Entry";
  194.     next = "Next";
  195.     ok = "OK";
  196.     options = "Options";
  197.     quit = " X ";
  198.     refresh = "Refresh";
  199.     remove = "Remove";
  200.    
  201.     setupText1 = "It seems like you've started Graffiti for";
  202.     setupText2 = "the first time on this computer.";
  203.     setupText3 = "This setup will create all neccessary data that";
  204.     setupText4 = "Graffiti needs to run and it allows you to set";
  205.     setupText5 = "specific settings.";
  206.    
  207.     skipSetup = "Skip setup (use default settings)";
  208.     chooseSettings1 = "Please specify these settings:";
  209.     chooseSettings2 = "(You can change them later if you want)";
  210.     showDataFolder = "Show Data folder";
  211.     hideDataFolder = "Hide Data folder";
  212.   };
  213. }
  214.  
  215. local Text = Languages["en-US"]
  216.  
  217. -- Sizes
  218. local maxX, maxY = 51, 19
  219. local Size = {
  220.   Button = { width = 10; height = 3; };
  221.   ProgressBar = { length = 10; };
  222.   Slider = { length = 10; };
  223.   DropDownList = { width=10 };
  224.   Container = { width = 20; height = 10; };
  225. }
  226.  
  227. -- Files Info
  228. local root = "/" -- The path where the data-folder will be created by default.
  229. local dataFolderPath = nil
  230. local LoadedFiles = {}
  231. local currentProject = "Default"
  232.  
  233. -- Converter
  234. local Converter = {}
  235.  
  236. -- API
  237. local initDone = false
  238. local isAPI = false -- Determines whether the program has been loaded as an API
  239. local VariableValues = {}
  240. local ProgressBarValues = {}
  241.  
  242. -- Editor
  243. local currentEditorAction = nil
  244. local editMode = false
  245. local lastWindow = nil
  246. local RightClickActions = {"Attributes", "Delete" }
  247. local saveAfterQuit = true
  248. local selectedObject = nil
  249. local selectedObjectDragged = false
  250.  
  251. -- Custom Windows
  252. local showCustomWindow = nil
  253. local ListEditorList = {}
  254. local CustomWindows = {}
  255. local CustomFunctions = {}
  256.  
  257. -- Tables
  258. local Args = { ... }
  259. local Sides = rs.getSides()
  260. local ObjectTypes = { "Button", "Text", "Variable", "ProgressBar", "Input", "List", "CheckBox", "RadioButton", "Slider", "DropDownList", "Panel", "ScrollView" }
  261. local EventTypes = { "quit", "button_clicked", "button_toggled", "selection_changed", "text_changed", "checked", "radio_changed", "slider_changed" }
  262. Objects = {}
  263. Windows = { Children = {} }
  264. local WindowBuffer = nil
  265. local DefaultButtons = {}
  266. local Shortcuts = {}
  267.  
  268. -- Other
  269. local quit = false
  270. local doLog = true -- Determines wheter a log file should be created or not.
  271. local logFileLoaded = false
  272. local out = term -- Output: either "term" or the monitor
  273. local outIsTerm = false
  274. local changeButtonColor = true
  275. local currentWindow = "Main"
  276.  
  277. -- Settings
  278. local Settings = {
  279.   root = root;
  280.   startupProject = currentProject;
  281.   language = "en-US";
  282.   colorTheme = "Default";
  283.   hideDataFolder = false;
  284. }
  285.  
  286. -- >> User Data: (This section is just for YOU! :D)
  287.  
  288. --[[ How to make your own functions:
  289. Normal "button_clicked" or "button_toggled" function:
  290. Note: toggleState is only needed if the buttons'
  291. funcType-attribute is set to "toggle function"
  292.  
  293. function UserData.<objID>(toggleState)
  294.   your code here
  295. end
  296.  
  297. If you don't want to edit this file then you can
  298. just load it as an API via "os.loadapi(Graffiti)"
  299. and use "Graffiti.pullEvent()".
  300. ]]
  301.  
  302. function UserData.test()
  303.   sleep(1)
  304. end
  305.  
  306. function UserData.refresh()
  307.   drawWindow()
  308. end
  309.  
  310. function UserData.toggleTest(toggleState)
  311.   rs.setOutput("front", toggleState)
  312. end
  313.  
  314. --[[ "Load" function
  315. You can specify a string in the "load" attribute
  316. of some objects.
  317. The function in the "UserData" folder gets called
  318. every time the object gets drawn.
  319. ]]
  320. function UserData.getRandomNumber()
  321.   return math.random(100)
  322. end
  323.  
  324. function UserData.getTime()
  325.   return textutils.formatTime(os.time(), true)
  326. end
  327.  
  328. --[[ WARNING!
  329. Everything below this comment
  330. shouldn't be edited!
  331. If you do so and the program doesn't work anymore
  332. then it's your fault!
  333. ]]
  334.  
  335. --[[ Displays the text, the content of a table
  336. or a star in the upper left corner until you press
  337. a key.]]
  338. function dBug(text)
  339.   out.setCursorPos(1, 1)
  340.  
  341.   if (text == nil) then
  342.     out.write("*")
  343.     getKeyInput()
  344.     out.setCursorPos(1, 1)
  345.     out.write(" ")
  346.     return
  347.   elseif (type(text) == "table") then
  348.     for key, value in pairs(text) do
  349.       print(key .. ": " .. tostring(value))
  350.     end
  351.   else
  352.     out.write(text)
  353.   end
  354.  
  355.   getKeyInput()
  356. end
  357.  
  358. -- >> Type extensions
  359.  
  360. function string:startsWith(text)
  361.   assert(self)
  362.   assert(text)
  363.  
  364.   local substring = text:sub(1, #text)
  365.   return (substring == text)
  366. end
  367.  
  368. function string:endsWith(text)
  369.   assert(self)
  370.   assert(text)
  371.  
  372.   local sStart, sEnd = string.find(self, text, (#text * -1))
  373.   return (sStart ~= nil)
  374. end
  375.  
  376. -- Checks whether the table contains the given content-parameter.
  377. function table:contains(content)
  378.   if (self and type(self) == "table") then
  379.     for _, value in pairs(self) do
  380.       if (value == content) then
  381.         return true
  382.       end
  383.     end
  384.    
  385.     return false
  386.   else
  387.     return false
  388.   end
  389. end
  390.  
  391. -- >> Files
  392. local Files = {}
  393.  
  394. -- Returns whether the filename ends with the
  395. -- given extension.
  396. function Files.endsWith(filename, extension)
  397.   assert(filename)
  398.   assert(extension)
  399.  
  400.   local sStart, sEnd = string.find(filename, extension, (#extension * -1))
  401.   return (sStart ~= nil)
  402. end
  403.  
  404. -- Serializes a normal or serialized table into a
  405. -- readable format which can be saved in a file.
  406. function Files.serialize(toSave)
  407.   local saveText
  408.   local serialized = ""
  409.   local indentation = 0
  410.   local indent = false
  411.  
  412.   if (type(toSave) == "string") then
  413.     saveText = toSave
  414.   elseif (type(toSave) == "table") then
  415.     saveText = textutils.serialize(toSave)
  416.   else
  417.     error("Can't save variable of type .. " .. type(toSave) .. "!", 1)
  418.   end
  419.  
  420.   for char in saveText:gmatch(".") do
  421.     if (char == "{") then
  422.       indentation = indentation + 1
  423.      
  424.       serialized = serialized .. char
  425.       serialized = serialized .. "\n"
  426.       indent = true
  427.     elseif (char == ",") then
  428.       serialized = serialized .. char .. "\n"
  429.       indent = true
  430.     elseif (char == "}") then
  431.       indentation = indentation - 1
  432.       serialized = serialized .. string.rep("\t", indentation)
  433.       serialized = serialized .. char
  434.       indent = true
  435.     else
  436.       if indent then
  437.         serialized = serialized .. string.rep("\t", indentation)
  438.         indent = false
  439.       end
  440.      
  441.       serialized = serialized .. char
  442.     end
  443.   end
  444.  
  445.   serialized = serialized:gsub(" ", "<SPACE>")
  446.   return serialized
  447. end
  448.  
  449. -- Unserializes a string that has been serialized
  450. -- using the "Files.serialize" function into a
  451. -- table.
  452. function Files.unserialize(str)
  453.   local saveText = str
  454.   saveText = saveText:gsub("\n", "")
  455.   saveText = saveText:gsub("\t", "")
  456.   saveText = saveText:gsub(" ", "")
  457.   saveText = saveText:gsub("<SPACE>", " ")
  458.   saveText = textutils.unserialize(saveText)
  459.  
  460.   return saveText
  461. end
  462.  
  463. -- Initializes the Graffiti data folder.
  464. function Files.init()
  465.   if (fs.exists(fs.combine(root, ".GraffitiData"))) then
  466.     dataFolderPath = fs.combine(root, ".GraffitiData")
  467.   elseif (fs.exists(fs.combine(root, "GraffitiData"))) then
  468.     dataFolderPath = fs.combine(root, "GraffitiData")
  469.   else
  470.     runSetup()
  471.   end
  472.  
  473.   for key, value in pairs(Files) do
  474.     if (type(value) == "table") then -- If it's an actual file type instead of a function.
  475.       if (value.autoLoad and value.load) then
  476.         Files.load(key)
  477.       end
  478.     end
  479.   end
  480. end
  481.  
  482. -- Saves all files with enabled "autoSave"
  483. -- attribute.
  484. function Files.save()
  485.   for _, file in pairs(Files) do
  486.     if (type(file) == "table") then -- If it's an actual file type instead of a function.
  487.       if (file.autoSave and file.save) then
  488.         file.save()
  489.       end
  490.     end
  491.   end
  492. end
  493.  
  494. -- Loads the file and all of its required files.
  495. function Files.load(fileType)
  496.   if not table.contains(LoadedFiles, fileType) then
  497.     local loadRequired = Files[fileType].loadRequired
  498.    
  499.     if (loadRequired) then
  500.       for _, value in pairs(loadRequired) do
  501.         Files.load(value)
  502.       end
  503.     end
  504.    
  505.     Files[fileType].load()
  506.     table.insert(LoadedFiles, fileType)
  507.   end
  508. end
  509.  
  510. -- Saves the table into a file in Graffitis' default format.
  511. function Files.saveTable(subDir, fileName, tbl)
  512.   local fileHandle = Files.createDataFile(subDir, fileName, "w")
  513.  
  514.   for key, value in pairs(tbl) do
  515.     fileHandle.writeLine(key .. "=" .. tostring(value))
  516.   end
  517.  
  518.   if fileHandle then
  519.     fileHandle.close()
  520.   end
  521. end
  522.  
  523. function Files.loadTable(subDir, fileName)
  524.   local fileHandle = Files.getDataFileHandle(subDir, fileName, "r", false)
  525.   local ret = {}
  526.  
  527.   if not fileHandle then
  528.     log("Can't get file handle of file \"" .. fileName .. "\"! Returning nil.", "WARNING")
  529.     return nil
  530.   end
  531.  
  532.   repeat
  533.     local line = fileHandle.readLine()
  534.    
  535.     if (line ~= nil and line ~= "" and string.find(line, "=")) then
  536.       local key, value = splitAt(line, "=")
  537.       ret[key] = value
  538.     end
  539.   until line == nil
  540.  
  541.   if fileHandle then
  542.     fileHandle.close()
  543.   end
  544.  
  545.   return ret
  546. end
  547.  
  548. -- Removes the content of a file.
  549. function Files.clear(subDir, fileName)
  550.   local fileHandle = Files.getDataFileHandle(subDir, fileName, "w", false)
  551.  
  552.   if fileHandle then
  553.     fileHandle.close()
  554.   end
  555. end
  556.  
  557. -- Returns the path to the sub-directory and
  558. -- creates the folders if necessary.
  559. function Files.getSubDirPath(subDir)
  560.   assert(subDir)
  561.  
  562.   local subDirPath = dataFolderPath
  563.  
  564.   for _, dir in pairs(subDir) do
  565.     subDirPath = fs.combine(subDirPath, dir)
  566.  
  567.     if (fs.exists(subDirPath)) then
  568.       if (not fs.isDir(subDirPath)) then
  569.         error("Can't create directory " .. dir .. "! A file with that name exists!", 2)
  570.       end
  571.     else
  572.       fs.makeDir(subDirPath)
  573.     end
  574.   end
  575.  
  576.   return subDirPath
  577. end
  578.  
  579. -- Returns the handle of an existing file or null.
  580. -- The file will automatically be created if
  581. -- autoCreate is true.
  582. function Files.getDataFileHandle(subDir, filename, mode, autoCreate)
  583.   assert(filename)
  584.   assert(mode)
  585.  
  586.   local ret
  587.   local path
  588.  
  589.   -- Create the path to the file if it doesn't exist.
  590.   if subDir then
  591.     local subDirPath
  592.    
  593.     if (type(subDir) == "string" and subDir ~= "") then
  594.       subDirPath = fs.combine(dataFolderPath, subDir)
  595.       path = fs.combine(subDirPath, filename)
  596.      
  597.       if (fs.exists(subDirPath)) then
  598.         if (not fs.isDir(subDirPath)) then
  599.           error("Can't create directory " .. subDir .. "! A file with that name exists!", 2)
  600.         end
  601.       else
  602.         fs.makeDir(subDirPath)
  603.       end
  604.     elseif (type(subDir) == "table") then
  605.       local subDirPath = Files.getSubDirPath(subDir)
  606.       path = fs.combine(subDirPath, filename)
  607.     end
  608.   else
  609.     path = fs.combine(dataFolderPath, filename)
  610.   end
  611.  
  612.   if (fs.exists(path) and fs.isReadOnly(path)) then
  613.     error("File " .. filePath .. " is readonly!", 2)
  614.   end
  615.  
  616.   if (fs.exists(path) or mode == "w") then
  617.     ret = fs.open(path, mode)
  618.   elseif autoCreate then
  619.     ret = fs.open(path, "w") or error("Can't open file " .. path, 2)
  620.   end
  621.  
  622.   return ret
  623. end
  624.  
  625. -- Creates or overrides a file and returns its handle.
  626. function Files.createDataFile(subDir, filename)
  627.   return Files.getDataFileHandle(subDir, filename, "w")
  628. end
  629.  
  630. -- Renames the file to the "newName"-parameter.
  631. -- Note to myself: Don't copy the "get the subDir"-part from this one.
  632. function Files.rename(subDir, filename, newName)
  633.   assert(subDir)
  634.   assert(filename)
  635.   assert(newName)
  636.  
  637.   local folderPath
  638.   local filePath
  639.  
  640.   if subDir then
  641.     local subDirPath
  642.    
  643.     if (type(subDir) == "string" and subDir ~= "") then
  644.       subDirPath = fs.combine(dataFolderPath, subDir)
  645.       folderPath = subDirPath
  646.       filePath = fs.combine(subDirPath, filename)
  647.      
  648.       if (fs.exists(subDirPath)) then
  649.         if (not fs.isDir(subDirPath)) then
  650.           error("Can't access directory " .. subDir .. "! A file with that name exists!", 2)
  651.         end
  652.       else
  653.         return
  654.       end
  655.     elseif (type(subDir) == "table") then
  656.       folderPath = Files.getSubDirPath(subDir)
  657.       filePath = fs.combine(folderPath, filename)
  658.     end
  659.   else
  660.     folderPath = dataFolderPath
  661.     filePath = fs.combine(dataFolderPath, filename)
  662.   end
  663.  
  664.   fs.move(filePath, fs.combine(folderPath, newName))
  665.  
  666.   log("Renamed window \"" .. filename .. "\" to \"" .. newName .. "\".")
  667. end
  668.  
  669. -- Removes the file.
  670. -- NOTE: The path accessing is slightly different.
  671. function Files.remove(subDir, filename)
  672.   local path
  673.  
  674.   if subDir then
  675.     local subDirPath
  676.    
  677.     if (type(subDir) == "string" and subDir ~= "") then
  678.       subDirPath = fs.combine(dataFolderPath, subDir)
  679.       path = fs.combine(subDirPath, filename)
  680.      
  681.       if (fs.exists(subDirPath)) then
  682.         if (not fs.isDir(subDirPath)) then
  683.           error("Can't access directory " .. subDir .. "! A file with that name exists!", 2)
  684.         end
  685.       else
  686.         return
  687.       end
  688.     elseif (type(subDir) == "table") then
  689.       local subDirPath = Files.getSubDirPath(subDir)
  690.       path = fs.combine(subDirPath, filename)
  691.     end
  692.   else
  693.     path = fs.combine(dataFolderPath, filename)
  694.   end
  695.  
  696.   fs.delete(path)
  697.  
  698.   log("Deleted window \"" .. path .. "\".")
  699. end
  700.  
  701. -- >> Version File
  702. Files.Version = {
  703.   autoSave = true;
  704.   autoLoad = true;
  705.   subDir = nil;
  706.   loadRequired = { "Project" };
  707.   name = "version"
  708. }
  709.  
  710. function Files.Version.save()
  711.   local fileHandle = Files.getDataFileHandle(Files.Version.subDir, Files.Version.name, "w")
  712.  
  713.   if fileHandle then
  714.     fileHandle.writeLine(version)
  715.     fileHandle.close()
  716.   end
  717. end
  718.  
  719. function Files.Version.load()
  720.   local fileHandle = Files.getDataFileHandle(Files.Version.subDir, Files.Version.name, "r", false)
  721.  
  722.   if fileHandle then
  723.     local fileVersion = fileHandle.readLine()
  724.    
  725.     if (fileVersion ~= version) then
  726.       Converter.run(fileVersion)
  727.     end
  728.    
  729.     fileHandle.close()
  730.   else
  731.     Converter.run()
  732.   end
  733. end
  734.  
  735. -- >> Project File
  736.  
  737. Files.Project = {
  738.   autoSave = true;
  739.   autoLoad = true;
  740.   subDir = "Projects"; -- Has to be a string.
  741.   loadRequired = { "Settings" };
  742.   extension = ".window";
  743. }
  744.  
  745. -- Saves the content of the Windows-table into the
  746. -- save file.
  747. function Files.Project.save()
  748.   for name, window in pairs(Windows.Children) do
  749.     if (type(window) == "table") then
  750.       local saveString = Files.serialize(window)
  751.       local fileHandle = Files.createDataFile({ Files.Project.subDir, currentProject }, name .. Files.Project.extension )
  752.      
  753.       fileHandle.write(saveString)
  754.       fileHandle.close()
  755.     end
  756.   end
  757. end
  758.  
  759. -- Loads the save file and puts the content into
  760. -- the Windows-table
  761. function Files.Project.load()
  762.   local path = fs.combine(dataFolderPath, Files.Project.subDir)
  763.   path = fs.combine(path, currentProject)
  764.  
  765.   if not path or not fs.isDir(path) then
  766.     return
  767.   end
  768.  
  769.   for _, filename in pairs(fs.list(path)) do
  770.     local filePath = fs.combine(path, filename)
  771.     local windowName = string.sub(filename, 1, #filename - #Files.Project.extension)
  772.    
  773.     if (not fs.isDir(filePath) and Files.endsWith(filename, Files.Project.extension)) then
  774.       local fileHandle = Files.getDataFileHandle({ Files.Project.subDir, currentProject }, filename, "r", false)
  775.      
  776.       if fileHandle then
  777.         local loadString = fileHandle.readAll()
  778.         loadString = Files.unserialize(loadString)
  779.        
  780.         if (loadString ~= nil and loadString ~= "") then
  781.           Windows.Children[windowName] = loadString
  782.         end
  783.        
  784.         fileHandle.close()
  785.       end
  786.     end
  787.   end
  788. end
  789.  
  790. -- >> Settings File
  791.  
  792. Files.Settings = {
  793.   autoSave = true;
  794.   autoLoad = true;
  795.   subDir = nil;
  796.   name = "Graffiti.cfg";
  797. }
  798.  
  799. function Files.Settings.init()
  800.   if not Settings then
  801.     return
  802.   end
  803.  
  804.   for key, value in pairs(Settings) do
  805.     if (key == "startupProject") then
  806.       currentProject = value
  807.     elseif (key == "language") then
  808.       Settings.language = value
  809.     elseif (key == "colorTheme" and ColorThemes[value]) then
  810.       ObjectColors = ColorThemes[value]
  811.     end
  812.   end
  813. end
  814.  
  815. -- Creates the settings file in the data-folder and fills it with default values.
  816. function Files.Settings.save()
  817.   Files.saveTable(Files.Settings.subDir, Files.Settings.name, Settings)
  818. end
  819.  
  820. -- Loads the settings file.
  821. function Files.Settings.load()
  822.   local tbl = Files.loadTable(Files.Settings.subDir, Files.Settings.name)
  823.  
  824.   if tbl then
  825.     for key, value in pairs(tbl) do
  826.       Settings[key] = value
  827.     end
  828.   end
  829.  
  830.   Files.Settings.init()
  831. end
  832.  
  833. -- >> Language File
  834.  
  835. function loadLanguage(language)
  836.   assert(language)
  837.  
  838.   if not Languages[language] then
  839.     --log("Tried to load non-existent language " .. tostring(language) .. ".", "WARNING")
  840.     return
  841.   end
  842.  
  843.   for key, value in pairs(Languages[language]) do
  844.     Text[key] = value
  845.   end
  846.  
  847.   --log("Loaded language \"" .. language .. "\".")
  848. end
  849.  
  850. Files.Language = {
  851.   autoSave = true;
  852.   autoLoad = true;
  853.   subDir = "Language";
  854.   extension = ".lang";
  855.   loadRequired = { "Settings" };
  856. }
  857.  
  858. function Files.Language.save()
  859.   local fileName = "Graffiti." .. Settings.language .. Files.Language.extension
  860.   Files.saveTable(Files.Language.subDir, fileName, Text)
  861. end
  862.  
  863. function Files.Language.load()
  864.   local path = fs.combine(dataFolderPath, Files.Language.subDir)
  865.  
  866.   if fs.exists(path) and fs.isDir(path) then
  867.     for _, filename in pairs(fs.list(path)) do
  868.       local fullPath = fs.combine(path, filename)
  869.      
  870.       if not (fs.isDir(fullPath) and
  871.           filename:startsWith("Graffiti.") and
  872.           filename:endsWith(".lang")) then
  873.         -- Gets the part between "Graffiti." and ".lang"
  874.         -- e.g. "Graffiti.en-US.lang" -> "en-US"
  875.         local languageDesc = filename:match("%.(.+)%.")
  876.         local languageTable = Files.loadTable(Files.Language.subDir, filename)
  877.        
  878.         Languages[languageDesc] = languageTable
  879.       end
  880.     end
  881.   end
  882.  
  883.   loadLanguage(Settings.language)
  884. end
  885.  
  886. -- >> Log File
  887. Files.Log = {
  888.   autoSave = false;
  889.   autoLoad = false;
  890.   subDir = "Log";
  891.   name = "Graffiti.log";
  892. }
  893.  
  894. -- Writes the text into a logfile.
  895. function log(text, logType)
  896.   if not doLog or not logFileLoaded then
  897.     return
  898.   end
  899.  
  900.   local logFileHandle = Files.getDataFileHandle(Files.Log.subDir, Files.Log.name, "a", true)
  901.  
  902.   if (type(text) == "table") then
  903.     local delimiter = ""
  904.    
  905.     logFileHandle.write((logType or "INFO") .. ": ")
  906.    
  907.     for key, value in pairs(text) do
  908.       logFileHandle.write(delimiter .. key .. "=" .. tostring(value))
  909.       delimiter = ", "
  910.     end
  911.    
  912.     logFileHandle.writeLine()
  913.   else
  914.     logFileHandle.writeLine((logType or "INFO") .. ": " .. tostring(text))
  915.   end
  916.  
  917.   logFileHandle.close()
  918. end
  919.  
  920. -- >> Converter
  921.  
  922. function Converter.updateObject(from, to, object)
  923.   if object.isContainer or object.children or object.Children then
  924.     Converter.updateContainer(from, to, object)
  925.   else
  926.     -- Object conversion goes here.
  927.   end
  928. end
  929.  
  930. function Converter.updateContainer(from, to, container)
  931.   if container.children then -- v1.7: The "children" attribute has been renamed to "Children"
  932.     container.Children = container.children
  933.     container.children = nil
  934.   end
  935.  
  936.   for objectKey, object in pairs(container.Children) do
  937.     Converter.updateObject(from, to, object)
  938.   end
  939. end
  940.  
  941. -- Checks all variables and tables for data from
  942. -- earlier versions which could make it
  943. -- incompatible with the newest version and
  944. -- updates those if possible.
  945. function Converter.run(oldVersion)
  946.   log("Running converter...")
  947.   log("Old version: " .. tostring(oldVersion) .. ".", "ATTR")
  948.   log("New version: " .. tostring(version) .. ".", "ATTR")
  949.  
  950.   Converter.updateContainer(oldVersion, version, Windows)
  951. end
  952.  
  953. -- >>> Shortcut functions (for key inputs)
  954. -- Not yet implemented! WIP!
  955. -- TODO: Fix bug where the key event won't disappear from the queue.
  956.  
  957. function callShortcut(key)
  958.   --[[if (key and Shortcuts[key]) then
  959.     Shortcuts[key]()
  960.   end]]
  961. end
  962.  
  963. -- S
  964. Shortcuts[31] = function()
  965.   out.clear()
  966.   out.setCursorPos(1, 1)
  967.   print("Saving windows...")
  968.  
  969.   Files.Project.save()
  970.  
  971.   print("Windows saved!")
  972.   print("Press any key to continue...")
  973. end
  974.  
  975. -- Q
  976. Shortcuts[16] = function()
  977.   quit = true
  978. end
  979.  
  980. -- >>> Object helper functions
  981.  
  982. function clearScreen()
  983.   out.setBackgroundColor(ObjectColors.background)
  984.   out.setTextColor(ObjectColors.text)
  985.   out.clear()
  986.   out.setCursorPos(1, 1)
  987. end
  988.  
  989. -- Returns whether the button with the given name
  990. -- has been pressed.
  991. function defaultButtonPressed(name, x, y)
  992.   assert(name)
  993.   assert(x)
  994.   assert(y)
  995.  
  996.   local window = getCurrentWindow()
  997.  
  998.   if (DefaultButtons[name]) then
  999.     local button = DefaultButtons[name]
  1000.     if (x >= button.left and x <= button.right and y >= button.top and y <= button.bottom) then
  1001.       return (button.required() or (editMode and not showCustomWindow))
  1002.     end
  1003.   else
  1004.     return false
  1005.   end
  1006. end
  1007.  
  1008. -- Returns the table containing the windows that
  1009. -- should be displayed.
  1010. function getWindowContainer()
  1011.   if showCustomWindow then
  1012.     return CustomWindows[showCustomWindow]
  1013.   else
  1014.     return Windows
  1015.   end
  1016. end
  1017.  
  1018. -- Returns the window object that is currently
  1019. -- displayed.
  1020. function getCurrentWindow()
  1021.   local windowContainer = getWindowContainer()
  1022.  
  1023.   return windowContainer.Children[currentWindow]
  1024. end
  1025.  
  1026. -- Used by the List and Selector objects to
  1027. -- determine how wide the list should be.
  1028. function getLongestString(strings)
  1029.   if (strings == nil or #strings == 0) then
  1030.     return 0
  1031.   end
  1032.  
  1033.   local ret = 0
  1034.   for key, value in pairs(strings) do
  1035.     length = string.len(value)
  1036.     if (length > ret) then
  1037.       ret = length
  1038.     end
  1039.   end
  1040.  
  1041.   return ret
  1042. end
  1043.  
  1044. -- Checks whether dir is a valid direction-string.
  1045. function isValidDirection(dir)
  1046.   if (dir ~= nil and
  1047.      (dir == "left" or
  1048.       dir == "up" or
  1049.       dir == "right" or
  1050.       dir == "down")) then
  1051.     return true
  1052.   end
  1053.  
  1054.   return false
  1055. end
  1056.  
  1057. --[[ Returns a table containing tables for all
  1058. files and directories in the given path:
  1059. Returns: fileName, filePath, isDir
  1060. ]]
  1061. function getFileList(path)
  1062.   local ret = {}
  1063.   local dirList = {}
  1064.   local fileList = {}
  1065.  
  1066.   for file in fs.list(path) do
  1067.     if (fs.isDir(file)) then
  1068.       table.insert(dirList, dirIndex, file)
  1069.       dirIndex = dirIndex + 1
  1070.     else
  1071.       table.insert(fileList, fileIndex, file)
  1072.       fileIndex = fileIndex + 1
  1073.     end
  1074.   end
  1075.   for _, file in ipairs(dirList) do
  1076.     table.insert(list, {fileName=file, filePath=fs.combine(path, file), isDir=true})
  1077.   end
  1078.   for _, file in ipairs(fileList) do
  1079.     table.insert(list, {fileName=file, filePath=fs.combine(path, file), isDir=false})
  1080.   end
  1081.  
  1082.   return ret
  1083. end
  1084.  
  1085. -- Returns "horizontal" or "vertical" depending on
  1086. -- the given direction.
  1087. function getOrientation(direction)
  1088.   local orientation = nil
  1089.  
  1090.   if (direction == "left" or direction == "right") then
  1091.     orientation = "horizontal"
  1092.   elseif (direction == "up" or direction == "down") then
  1093.     orientation = "vertical"
  1094.   end
  1095.  
  1096.   return orientation
  1097. end
  1098.  
  1099. -- Checks whether the eventTypes table contains
  1100. -- the given event type.
  1101. function eventTypeExists(eventType)
  1102.   if (eventType == nil) then
  1103.     return true
  1104.   end
  1105.  
  1106.   for _, event in pairs(EventTypes) do
  1107.     if (event == eventType) then
  1108.       return true
  1109.     end
  1110.   end
  1111.  
  1112.   return false
  1113. end
  1114.  
  1115. -- Returns the size that a buffer needs to have
  1116. -- to contain all children of a container.
  1117. function getNecessaryBufferSize(Children, minWidth, minHeight)
  1118.   local width, height = (minWidth or 0), (minHeight or 0)
  1119.  
  1120.   for _, child in pairs(Children) do
  1121.     local right = child.width and child.x + child.width or child.x
  1122.     local bottom = child.height and child.y + child.height or child.y
  1123.    
  1124.     width = (right > width) and right or width
  1125.     height = (bottom > height) and bottom or height
  1126.   end
  1127.  
  1128.   return width, height
  1129. end
  1130.  
  1131. -- Returns a table containing the position and
  1132. -- size that the scroll bar of a ScrollView object
  1133. -- should have.
  1134. function getScrollBarInfo(pos, containerSize, bufferSize)
  1135.   local ret = {}
  1136.   local maxScrollBarHeight = containerSize - 4
  1137.  
  1138.   local scrollCount = bufferSize - containerSize + 1 -- How often can the user scroll?
  1139.   if (scrollCount < 0) then
  1140.     scrollCount = 0
  1141.   end
  1142.  
  1143.   local scrollBarSize = math.ceil(maxScrollBarHeight * (1 / (scrollCount + 1)))
  1144.   local scrollBarPos
  1145.   if (scrollCount == 0) then
  1146.     scrollBarPos = 0
  1147.   --elseif (pos == scrollCount) then
  1148.     --scrollBarPos = maxScrollBarHeight - scrollBarSize
  1149.   else
  1150.     scrollBarPos = math.floor((maxScrollBarHeight / (scrollCount + 1)) * pos)
  1151.   end
  1152.  
  1153.   ret.size = scrollBarSize
  1154.   ret.pos = scrollBarPos
  1155.  
  1156.   return ret
  1157. end
  1158.  
  1159. -- >>> Path
  1160.  
  1161. Path = {}
  1162.  
  1163. -- Returns the container which is at the paths
  1164. -- nest level.
  1165. function Path:getContainerAt(nestLevel)
  1166.   local container = getCurrentWindow()
  1167.  
  1168.   for i = 1, nestLevel do
  1169.     if (container.Children[self[i]] and container.Children[self[i]].isContainer) then
  1170.       container = container.Children[self[i]]
  1171.     else
  1172.       return nil -- NOTE: was "break" before
  1173.     end
  1174.   end
  1175.  
  1176.   return container
  1177. end
  1178.  
  1179. -- Returns the object which is represented by the
  1180. -- path.
  1181. function Path:getObject()
  1182.   local container = Path.getContainerAt(self, #self - 1)
  1183.   return container.Children[self[#self]]
  1184. end
  1185.  
  1186. -- Returns the coordinates which are relative to
  1187. -- the last container represented by the path.
  1188. function Path:getRelativePos(x, y, checkFullPath)
  1189.   --log("Path.getRelativePos", "FUNC")
  1190.   --log("x: " .. x .. ", y: " .. y .. ", checkFullPath: " .. tostring(checkFullPath) .. ".", "ATTR")
  1191.  
  1192.   local container
  1193.   local limit = checkFullPath and #self or #self - 1
  1194.  
  1195.   for i = 1, limit do
  1196.     container = Path.getContainerAt(self, i)
  1197.    
  1198.     if container then
  1199.       x, y = Objects.Container.getRelativePos(container, x, y)
  1200.     else
  1201.       break
  1202.     end
  1203.   end
  1204.  
  1205.   return x, y
  1206. end
  1207.  
  1208. --[[ Generates a table of keys representing the
  1209. nesting of the containers that have been clicked.
  1210. Returns: e.g. {containeID, subContainerID}]]
  1211. Path.getContainerPath = function(x, y)
  1212.   local containerPath = {}
  1213.   local currentContainer = getCurrentWindow()
  1214.   local finished = false
  1215.   local nestLevel = 1
  1216.  
  1217.   while not finished do
  1218.     finished = true -- Set to true so that the loop stops when no container is found.
  1219.     for objectID, object in pairs(currentContainer.Children) do
  1220.       if finished and object.isContainer then
  1221.         if (Objects.Container.contentAreaClicked(object, x, y)) then
  1222.           containerPath[nestLevel] = objectID
  1223.           currentContainer = currentContainer.Children[objectID]
  1224.           x, y = Objects.Container.getRelativePos(currentContainer, x, y)
  1225.           finished = false
  1226.         end
  1227.       end
  1228.     end
  1229.    
  1230.     nestLevel = nestLevel + 1
  1231.   end
  1232.  
  1233.   return containerPath
  1234. end
  1235.  
  1236. -- >>> Buffer
  1237.  
  1238. Buffer = {
  1239.   bufferTable = {};
  1240.   width = 0;
  1241.   height = 0;
  1242. }
  1243.  
  1244. function Buffer.newPixel(backCol, textCol, char, path)
  1245.   local ret = {}
  1246.   ret.background = backCol or ObjectColors.background
  1247.   ret.text = textCol or ObjectColors.text
  1248.   ret.char = (type(char) == "string" and char ~= "") and char or " "
  1249.   ret.path = path or nil
  1250.   if (backCol or textCol or char ~= " " or path) then
  1251.     ret.draw = true
  1252.   else
  1253.     ret.draw = false
  1254.   end
  1255.  
  1256.   return ret;
  1257. end
  1258.  
  1259. function Buffer:new(object)
  1260.   object = object or {}
  1261.   setmetatable(object, self)
  1262.   self.__index = self
  1263.   return object
  1264. end
  1265.  
  1266. function Buffer:init(width, height, path, backCol, textCol)
  1267.   self.width = width or error("Can't initialize buffer because the width didn't get specified!")
  1268.   self.height = height or error("Can't initialize buffer because the height didn't get specified!")
  1269.   self.bufferTable = {}
  1270.  
  1271.   for col = 1, width do
  1272.     self.bufferTable[col] = {}
  1273.     for row = 1, height do
  1274.       self.bufferTable[col][row] = self.newPixel(backCol, textCol, " ", path)
  1275.     end
  1276.   end
  1277. end
  1278.  
  1279. function Buffer:trim(left, top, right, bottom)
  1280.   left = (left < 0) and 0 or left
  1281.   top = (top < 0) and 0 or top
  1282.   right = (right < 0) and 0 or right
  1283.   bottom = (bottom < 0) and 0 or bottom
  1284.  
  1285.   local width = self.width - left - right
  1286.   local height = self.height - top - bottom
  1287.   local trimmed = Buffer:new()
  1288.   trimmed:init(width, height)
  1289.  
  1290.   for col = 1, width do
  1291.     for row = 1, height do
  1292.       trimmed:setPixel(col, row, self.bufferTable[left + col][top + row])
  1293.     end
  1294.   end
  1295.  
  1296.   return trimmed
  1297. end
  1298.  
  1299. function Buffer:draw(x, y)
  1300.   x = x or 1
  1301.   y = y or 1
  1302.   local currentPixel
  1303.  
  1304.   for col = 0, self.width - 1 do
  1305.     for row = 0, self.height - 1 do
  1306.       currentPixel = self.bufferTable[col+1][row+1]
  1307.       if (currentPixel.draw) then
  1308.         out.setCursorPos(x + col, y + row)
  1309.         out.setBackgroundColor(currentPixel.background)
  1310.         out.setTextColor(currentPixel.text)
  1311.         out.write(currentPixel.char)
  1312.         currentPixel.draw = false
  1313.       end
  1314.     end
  1315.   end
  1316. end
  1317.  
  1318. function Buffer:setBackgroundColor(color)
  1319.   for col = 1, width do
  1320.     for row = 1, height do
  1321.       self.bufferTable[col][row].background = color
  1322.     end
  1323.   end
  1324. end
  1325.  
  1326. function Buffer:setTextColor(color)
  1327.   for col = 1, width do
  1328.     for row = 1, height do
  1329.       self.bufferTable[col][row].text = color
  1330.     end
  1331.   end
  1332. end
  1333.  
  1334. function Buffer:setPixel(x, y, pixel)
  1335.   assert(x)
  1336.   assert(y)
  1337.   assert(pixel)
  1338.  
  1339.   if (x > 0 and x <= self.width and y > 0 and y <= self.height) then
  1340.     for key, value in pairs(pixel) do
  1341.       if value then
  1342.         self.bufferTable[x][y][key] = value
  1343.       end
  1344.     end
  1345.   end
  1346. end
  1347.  
  1348. function Buffer:addText(x, y, text)
  1349.   assert(x)
  1350.   assert(y)
  1351.   assert(text)
  1352.  
  1353.   for char in tostring(text):gmatch(".") do
  1354.     self:setPixel(x, y, { char=char })
  1355.     x = x + 1
  1356.   end
  1357. end
  1358.  
  1359. function Buffer:addBuffer(x, y, buffer)
  1360.   assert(x)
  1361.   assert(y)
  1362.   assert(buffer)
  1363.   for col = 1, buffer.width do
  1364.     for row = 1, buffer.height do
  1365.       self:setPixel(x + col - 1, y + row - 1, buffer.bufferTable[col][row])
  1366.     end
  1367.   end
  1368. end
  1369.  
  1370. -- Adds a border to the container buffer.
  1371. function Buffer:makeBorder(path, color)
  1372.   local width, height = self.width, self.height
  1373.  
  1374.   self:addBuffer(1, 1, Objects.Line.get("horizontal", width, color, path))
  1375.   self:addBuffer(1, height, Objects.Line.get("horizontal", width, color, path))
  1376.   self:addBuffer(1, 1, Objects.Line.get("vertical", height, color, path))
  1377.   self:addBuffer(width, 1, Objects.Line.get("vertical", height, color, path))
  1378. end
  1379.  
  1380. -- >>> Objects
  1381.  
  1382. -- Returns a new object with its default attributes
  1383. -- depending on the object type.
  1384. -- Note: Don't call this function, use Objects.create!
  1385. function Objects.new(objectType, x, y)
  1386.   assert(objectType)
  1387.   assert(x)
  1388.   assert(y)
  1389.  
  1390.   log("Creating new object. Type: " .. objectType .. ", position: " .. x .. ", " .. y .. ".", "INFO")
  1391.  
  1392.   local object = {}
  1393.   local path = Path.getContainerPath(x, y)
  1394.   local relativeX, relativeY = Path.getRelativePos(path, x, y, true)
  1395.  
  1396.   object.objID = "new" .. objectType
  1397.   object.objType = objectType
  1398.   object.path = path
  1399.   local modX, modY = Objects.getPosModifier(object, true)
  1400.   object.x = relativeX + (modX * -1)
  1401.   object.y = relativeY + (modY * -1)
  1402.   object.absoluteX = x + (modX * -1)
  1403.   object.absoluteY = y + (modY * -1)
  1404.  
  1405.   local maxWidth = maxX - x
  1406.   local maxHeight = maxY - y
  1407.  
  1408.   if (Objects[objectType] and Objects[objectType].new) then
  1409.     Objects[objectType].new(object, maxWidth, maxHeight)
  1410.   elseif (Objects.Container[objectType]) then
  1411.     Objects.Container.new(object, maxWidth, maxHeight)
  1412.   else
  1413.     Objects.Unknown.new(objectType)
  1414.   end
  1415.  
  1416.   return object
  1417. end
  1418.  
  1419. -- Creates a new object at the given position with
  1420. -- its default attributes.
  1421. function Objects.create(objType, x, y)
  1422.   local object = Objects.new(objType, x, y)
  1423.   local container = Path.getContainerAt(object.path, #object.path)
  1424.   local key = Objects.Container.getNextFreeKey(container)
  1425.   table.insert(object.path, key)
  1426.   table.insert(container.Children, key, object)
  1427.  
  1428.   if not showCustomWindow then
  1429.     WindowBuffer:addBuffer(x, y, Objects.get(object))
  1430.     Objects.draw(object)
  1431.   end
  1432.  
  1433.   return object
  1434. end
  1435.  
  1436. -- Creates the default object and changes the
  1437. -- specified attributes to the given ones.
  1438. function Objects.createCustom(objType, x, y, attributes)
  1439.   local object = Objects.create(objType, x, y)
  1440.  
  1441.   for key, value in pairs(attributes) do
  1442.     object[key] = value
  1443.   end
  1444.  
  1445.   return object
  1446. end
  1447.  
  1448. function Objects.init()
  1449.   log("Initializing Objects...", "INFO")
  1450.  
  1451.   for _, window in pairs(Windows.Children) do
  1452.     for _, object in pairs(window.Children) do
  1453.       if (type(object) == "table") then
  1454.         local objType = object.objType
  1455.        
  1456.         if (Objects[objType] and Objects[objType].init) then
  1457.           log("Initializting " .. objType .. " " .. object.objID .. ".", "INFO")
  1458.           Objects[objType].init(object)
  1459.         elseif (object.isContainer) then
  1460.           Objects.Container.init(object)
  1461.         end
  1462.       end
  1463.     end
  1464.   end
  1465. end
  1466.  
  1467. -- Returns the x and y values which should be
  1468. -- added to an objects' position to match the
  1469. -- modifications of its parents (e.g. ScrollView)
  1470. function Objects.getPosModifier(self, checkFullPath)
  1471.   assert(self)
  1472.  
  1473.   local x, y = 0, 0
  1474.   local maxNestLevel = checkFullPath and #self.path or #self.path - 1
  1475.  
  1476.   if (maxNestLevel > 0) then
  1477.     for nestLevel = 1, maxNestLevel do
  1478.       local object = Path.getContainerAt(self.path, nestLevel)
  1479.       local modX, modY = Objects.Container.getPosModifier(object, x, y)
  1480.       x, y = x + modX, y + modY
  1481.     end
  1482.   end
  1483.  
  1484.   return x, y
  1485. end
  1486.  
  1487. -- Returns the relative position of the object
  1488. -- including the possible modifications of its
  1489. -- containers.
  1490. function Objects.getRelativePos(self)
  1491.   assert(self)
  1492.  
  1493.   local x, y = self.x, self.y
  1494.   local modX, modY = Objects.getPosModifier(self)
  1495.  
  1496.   return x + modX, y + modY
  1497. end
  1498.  
  1499. -- Returns the absolute position of the object
  1500. -- including the possible modifications of its
  1501. -- containers.
  1502. function Objects.getAbsolutePos(self)
  1503.   assert(self)
  1504.  
  1505.   local x, y = self.absoluteX, self.absoluteY
  1506.   local modX, modY = Objects.getPosModifier(self)
  1507.  
  1508.   return x + modX, y + modY
  1509. end
  1510.  
  1511. function Objects.isClicked(self, x, y)
  1512.   assert(self)
  1513.   assert(x)
  1514.   assert(y)
  1515.  
  1516.   return (x >= self.absoluteX and
  1517.       x <= self.absoluteX + self.width - 1 and
  1518.       y >= self.absoluteY and
  1519.       y <= self.absoluteY + self.height - 1)
  1520. end
  1521.  
  1522. -- Returns the buffer of the object.
  1523. function Objects.get(self, param)
  1524.   assert(self)
  1525.  
  1526.   --log("Getting " .. self.objType .. " object. ID: \"" .. self.objID .. "\", param: \"" .. tostring(param) .. "\"", "INFO")
  1527.  
  1528.   if (not editMode and not param and self.load) then
  1529.     if UserData[self.load] then
  1530.       param = UserData[self.load]()
  1531.       log("Loaded value of " .. self.objType .. " \"" .. self.objID .. "\":" .. param .. ".")
  1532.     end
  1533.   end
  1534.  
  1535.   local objType = self.objType or "Unknown"
  1536.   if (self.isContainer) then
  1537.     return Objects.Container.get(self, param)
  1538.   elseif (Objects[objType] and Objects[objType].get) then
  1539.     return Objects[objType].get(self, param)
  1540.   else
  1541.     error("Can't get buffer of object type \"" .. objType .. "\"!")
  1542.   end
  1543. end
  1544.  
  1545. --[[ Used to return the buffer of an object which
  1546. shows the user where to click to interact with
  1547. the object in a certain way.
  1548. (e.g. scrolling the ScrollView)
  1549. ]]
  1550. function Objects.addMarker(self, buffer)
  1551.   assert(self)
  1552.  
  1553.   log("Objects.addMarker", "FUNC")
  1554.   log(self.objType .. " ID: \"" .. self.objID .. "\".", "ATTR")
  1555.  
  1556.   if (Objects[self.objType] and Objects[self.objType].addMarker) then
  1557.     Objects[self.objType].addMarker(self, buffer)
  1558.   elseif (Objects.Container[self.objType] and Objects.Container[self.objType].addMarker) then
  1559.     Objects.Container[self.objType].addMarker(self, buffer)
  1560.   end
  1561. end
  1562.  
  1563. function Objects.remove(self)
  1564.   assert(self)
  1565.  
  1566.   log("Objects.remove", "FUNC")
  1567.   log(self.objType .. " ID: \"" .. self.objID .. "\".")
  1568.  
  1569.   local path = self.path
  1570.   local objKey = path[#path]
  1571.   local container = Path.getContainerAt(self.path, #self.path - 1)
  1572.  
  1573.   container.Children[objKey] = nil
  1574.  
  1575.   for i = #path - 2, 0, -1 do
  1576.     local parentContainer = Path.getContainerAt(self.path, i)
  1577.     parentContainer.Children[path[i + 1]] = container
  1578.     container = parentContainer
  1579.   end
  1580.  
  1581.   Windows.Children[currentWindow] = container
  1582. end
  1583.  
  1584. -- Draws the object.
  1585. function Objects.draw(self, param, drawMarker)
  1586.   assert(self)
  1587.  
  1588.   log("Drawing " .. self.objType .. ". ID: " .. self.objID .. ", param: \"" .. tostring(param) .. "\"", "ATTR")
  1589.  
  1590.   local buffer = Objects.get(self, param)
  1591.   if drawMarker then
  1592.     Objects.addMarker(self, buffer)
  1593.   end
  1594.  
  1595.   if (#self.path > 1) then
  1596.     local container = getCurrentWindow()
  1597.     local x, y = self.absoluteX, self.absoluteY
  1598.     Objects.Container.drawBuffer(container, buffer, self.path, x, y, x, y, 0)
  1599.   else
  1600.     buffer:draw(self.x, self.y)
  1601.   end
  1602.  
  1603.   term.setBackgroundColor(ObjectColors.background)
  1604.   term.setTextColor(ObjectColors.text)
  1605.   drawDefaultButtons()
  1606. end
  1607.  
  1608. -- Returns the resulting event and a "params"-array.
  1609. function Objects.click(self, x, y)
  1610.   assert(self)
  1611.  
  1612.   log(self.objType .. " \"" .. self.objID .. "\" clicked at " .. x .. ", " .. y .. ".", "INFO")
  1613.  
  1614.   local objType = self.objType
  1615.   local event, params
  1616.   if Objects[objType] and Objects[objType].click then
  1617.     event, params = Objects[objType].click(self, x, y)
  1618.   elseif (self.isContainer and Objects.Container[objType] and Objects.Container[objType].click) then
  1619.     event, params = Objects.Container[objType].click(self, x, y)
  1620.   end
  1621.  
  1622.   return event, params
  1623. end
  1624.  
  1625. -- Returns the resulting event and a "params"-array.
  1626. function Objects.drag(self, x, y)
  1627.   assert(self)
  1628.  
  1629.   log(self.objType .. " \"" .. self.objID .. "\" dragged at " .. x .. ", " .. y .. ".", "INFO")
  1630.  
  1631.   local objType = self.objType
  1632.   local event, params
  1633.   if Objects[objType] and Objects[objType].drag then
  1634.     event, params = Objects[objType].drag(self, x, y)
  1635.   elseif (self.isContainer and Objects.Container[objType] and Objects.Container[objType].drag) then
  1636.     event, params = Objects.Container[objType].drag(self, x, y)
  1637.   end
  1638.  
  1639.   return event, params
  1640. end
  1641.  
  1642. -- Gets called when an object gets clicked in edit
  1643. -- mode. Returns false if the object should be
  1644. -- edited as usual (move, scale).
  1645. function Objects.editorClick(self, x, y)
  1646.   assert(self)
  1647.   assert(x)
  1648.   assert(y)
  1649.  
  1650.   log("Objects.editorClick", "FUNC")
  1651.   log(self.objType .. " ID: \"" .. self.objID .. "\", x: " .. x .. ", y: " .. y .. ".", "ATTR")
  1652.  
  1653.   if (Objects[self.objType] and Objects[self.objType].editorClick) then
  1654.     return Objects[self.objType].editorClick(self, x, y)
  1655.   elseif (Objects.Container[self.objType] and Objects.Container[self.objType].editorClick) then
  1656.     return Objects.Container[self.objType].editorClick(self, x, y)
  1657.   else
  1658.     return false
  1659.   end
  1660. end
  1661.  
  1662. -- Moves the object.
  1663. function Objects.move(self, addX, addY)
  1664.   assert(self)
  1665.  
  1666.   log("Objects.move", "FUNC")
  1667.   log(self.objType .. " ID: " .. self.objID .. ", x: " .. x .. ", y: " .. y .. ".", "ATTR")
  1668.  
  1669.   local objType = self.objType
  1670.  
  1671.   if (Objects[objType] and Objects[objType].move) then
  1672.     Objects[objType].move(self, addX, addY)
  1673.   elseif (self.isContainer) then
  1674.     self.x = self.x + addX
  1675.     self.y = self.y + addY
  1676.     Objects.Container.move(self, addX, addY)
  1677.   else
  1678.     self.x = self.x + addX
  1679.     self.y = self.y + addY
  1680.     self.absoluteX = self.absoluteX + addX
  1681.     self.absoluteY = self.absoluteY + addY
  1682.   end
  1683. end
  1684.  
  1685. -- Scales the object.
  1686. function Objects.scale(self, x, y)
  1687.   assert(self)
  1688.  
  1689.   log("Objects.scale", "FUNC")
  1690.   log(self.objType .. " ID: " .. self.objID .. ", x: " .. x .. ", y: " .. y .. ".", "ATTR")
  1691.   log("Object pos: " .. self.x .. ", " .. self.y, "DEBUG")
  1692.  
  1693.   local objType = self.objType
  1694.  
  1695.   if (Objects[objType] and Objects[objType].scale) then
  1696.     Objects[objType].scale(self, x, y)
  1697.   elseif (self.isContainer and Objects.Container[objType].scale) then
  1698.     Objects.Container[objType].scale(self, x, y)
  1699.   else
  1700.     local objX, objY = self.x, self.y
  1701.     if (x > objX and y >= objY) then
  1702.       self.width = x - objX + 1
  1703.       self.height = y - objY + 1
  1704.     end
  1705.   end
  1706. end
  1707.  
  1708. --[[ Returns the position of the pixel which shows
  1709. the user where to click when he wants to move the
  1710. object.]]
  1711. function Objects.getMovePos(self)
  1712.   assert(self)
  1713.  
  1714.   local objType = self.objType
  1715.   if (Objects[objType] and Objects[objType].getMovePos) then
  1716.     return Objects[objType].getMovePos(self)
  1717.   elseif (self.isContainer and Objects.Container[objType] and Objects.Container[objType].getMovePos) then
  1718.     return Objects.Container[objType].getMovePos(self)
  1719.   else
  1720.     return Objects.getAbsolutePos(self)
  1721.   end
  1722. end
  1723.  
  1724. --[[ Returns the position of the pixel which shows
  1725. the user where to click when he wants to scale the
  1726. object.]]
  1727. function Objects.getScalePos(self)
  1728.   if not self.canScale then
  1729.     error("Tried to get scale pos of an unscalable object!")
  1730.   end
  1731.  
  1732.   local objType = self.objType
  1733.   if (Objects[objType] and Objects[objType].getScalePos) then
  1734.     return Objects[objType].getScalePos(self)
  1735.   elseif (self.isContainer and Objects.Container[objType].getScalePos) then
  1736.     Objects.Container[objType].getScalePos(self)
  1737.   else
  1738.     local x, y = Objects.getAbsolutePos(self)
  1739.     return x + self.width - 1, y + self.height - 1
  1740.   end
  1741. end
  1742.  
  1743. -- Returns the left, top, right and bottom coordinates of the object.
  1744. function Objects.getDimensions(self)
  1745.   assert(self)
  1746.  
  1747.   local objType = self.objType
  1748.   if (Objects[objType] and Objects[objType].getDimensions) then
  1749.     return Objects[objType].getDimensions(self)
  1750.   elseif (self.isContainer and Objects.Container[objType] and Objects.Container[objType].getDimensions) then
  1751.     return Objects.Container[objType].getDimensions(self)
  1752.   else
  1753.     local left, top, right, bottom
  1754.     left, top = self.x, self.y
  1755.     right = left + self.width - 1
  1756.     bottom = top + self.height - 1
  1757.     return left, top, right, bottom
  1758.   end
  1759. end
  1760.  
  1761. function Objects.attributeChanged(self, key, oldValue, newValue)
  1762.   assert(self)
  1763.  
  1764.   local objType = self.objType
  1765.   if (Objects[objType] and Objects[objType].attributeChanged) then
  1766.     Objects[objType].attributeChanged(self, key, oldValue, newValue)
  1767.   elseif (self.isContainer and Objects.Container[objType] and Objects.Container[objType].attributeChanged) then
  1768.     Objects.Container[objType].attributeChanged(self, key, oldValue, newValue)
  1769.   end
  1770. end
  1771.  
  1772. function Objects.editAttributes(self)
  1773.   assert(self)
  1774.  
  1775.   log("Editing attributes of " .. self.objType .. " \"" .. self.objID .. "\".", "INFO")
  1776.  
  1777.   local objType = self.objType
  1778.   local attributes
  1779.  
  1780.   if (Objects[objType] and Objects[objType].attributes) then
  1781.     attributes = Objects[objType].attributes
  1782.   elseif (Objects.Container[objType] and Objects.Container[objType].attributes) then
  1783.     attributes = Objects.Container[objType].attributes
  1784.   else
  1785.     attributes = {}
  1786.   end
  1787.  
  1788.   local finished = false
  1789.   while not finished do
  1790.     local yPos = 2
  1791.     local top = yPos
  1792.     clearScreen()
  1793.    
  1794.     for index = 1, #attributes do
  1795.       local description = attributes[index].description
  1796.       local value = self[attributes[index].attrName]
  1797.       local required = attributes[index].required
  1798.      
  1799.       out.setCursorPos(2, yPos)
  1800.      
  1801.       local requirementMet = false
  1802.      
  1803.       if required then
  1804.         local reqAttrName = required.attrName
  1805.         local reqValue = required.value
  1806.        
  1807.         for reqAttrKey, reqAttributeValue in pairs(self) do
  1808.           if (reqAttrKey == reqAttrName and reqAttributeValue == reqValue) then
  1809.             requirementMet = true
  1810.           end
  1811.         end
  1812.       end
  1813.      
  1814.       if required and not requirementMet then
  1815.         out.setBackgroundColor(ObjectColors.disabled)
  1816.         out.setTextColor(ObjectColors.disabledText)
  1817.       end
  1818.      
  1819.       out.write(description .. ": " .. tostring(value))
  1820.      
  1821.       if required and not requirementMet then
  1822.         out.setBackgroundColor(ObjectColors.background)
  1823.         out.setTextColor(ObjectColors.text)
  1824.       end
  1825.      
  1826.       yPos = yPos + 1
  1827.     end
  1828.    
  1829.     drawSimpleButton(2, yPos + 1, Text.done)
  1830.     out.setBackgroundColor(ObjectColors.background)
  1831.    
  1832.     local bottom = yPos - 1
  1833.     local x, y, mouseButton = getCursorInput()
  1834.    
  1835.     if y >= top and y <= bottom then
  1836.       local selectedAttrIndex = y - top + 1
  1837.       local attribute = attributes[selectedAttrIndex]
  1838.       local attrName = attributes[selectedAttrIndex].attrName
  1839.       local attrType = attribute.attrType
  1840.       local attrValue = self[attrName]
  1841.      
  1842.       log("Editing \"" .. attrType .. "\" attribute \"" .. attrName .. "\".", "INFO")
  1843.      
  1844.       if not outIsTerm then
  1845.         drawPixel(1, y, colors.yellow)
  1846.       end
  1847.      
  1848.       if (attrType == "text") then
  1849.         if not outIsTerm then
  1850.           drawPixel(1, y, ObjectColors.Editor.textInputMarker)
  1851.         end
  1852.        
  1853.         local left = #attribute.description + 4
  1854.         local lineWidth = maxX - left + 1
  1855.        
  1856.         out.setCursorPos(left, y)
  1857.         out.write(string.rep(" ", lineWidth))
  1858.         out.setCursorPos(left, y)
  1859.        
  1860.         local input = readUserInput("Enter a vlaue for the \"" .. attribute.description .. "\" value.")
  1861.        
  1862.         if input then
  1863.           self[attrName] = input
  1864.         end
  1865.       elseif (attrType == "list") then
  1866.         local nextAttr = attribute.list[1]
  1867.         local selectAttribute = false
  1868.        
  1869.         for _, value in ipairs(attribute.list) do
  1870.           if (selectAttribute) then
  1871.             nextAttr = value
  1872.             break
  1873.           elseif (attrValue == value) then
  1874.             selectAttribute = true
  1875.           end
  1876.         end
  1877.        
  1878.         self[attrName] = nextAttr
  1879.       elseif (attrType == "number") then
  1880.         if not outIsTerm then
  1881.           drawPixel(1, y, ObjectColors.Editor.textInputMarker)
  1882.         end
  1883.        
  1884.         local left = #attribute.description + 4
  1885.         local lineWidth = maxX - left + 1
  1886.        
  1887.         out.setCursorPos(left, y)
  1888.         out.write(string.rep(" ", lineWidth))
  1889.         out.setCursorPos(left, y)
  1890.        
  1891.         local input = readUserInput("Enter a number for the \"" .. attribute.description .. "\" value.")
  1892.        
  1893.         if input and tonumber(input) then
  1894.           self[attrName] = tonumber(input)
  1895.         end
  1896.       elseif (attrType == "bool") then
  1897.           self[attrName] = not attrValue
  1898.       elseif (attrType == "table") then
  1899.         editMode = false
  1900.         ListEditorList = {}
  1901.        
  1902.         if self[attrName] then
  1903.           for key, value in pairs(self[attrName]) do
  1904.             ListEditorList[key] = value
  1905.           end
  1906.         else
  1907.           ListEditorList = {}
  1908.         end
  1909.        
  1910.         local result = CustomWindows.draw("ListEditor", "Main")
  1911.        
  1912.         if result then
  1913.           self[attrName] = {}
  1914.          
  1915.           for key, value in pairs(ListEditorList) do
  1916.             self[attrName][key] = value
  1917.           end
  1918.         end
  1919.        
  1920.         editMode = true
  1921.         currentWindow = lastWindow
  1922.       end
  1923.      
  1924.       Objects.attributeChanged(self, attrName, attrValue, self[attrName])
  1925.     elseif (y == yPos + 1 and x >= 2 and x <= 1 + string.len(Text.done)) then -- Done button
  1926.       finished = true
  1927.     end
  1928.   end
  1929. end
  1930.  
  1931. -- >> Unknown
  1932. Objects.Unknown = {}
  1933. Objects.Unknown.new = function(objType)
  1934.   objType = objType and " \"" .. objType .. "\"" or ""
  1935.   error("Tried to create new object with unknown object ID" .. objType .. "!")
  1936. end
  1937.  
  1938. Objects.Unknown.get = function(self)
  1939.   error("Tried to get the buffer of and unknown object!")
  1940. end
  1941.  
  1942. -- >> Button
  1943.  
  1944. Objects.Button = {}
  1945. Objects.Button.attributes = {
  1946.   { description = "Object ID", attrName = "objID", attrType = "text" };
  1947.   { description = "Text", attrName = "text", attrType = "text" };
  1948.   { description = "Function type", attrName = "funcType", attrType = "list", list = { "switch", "function", "toggle function" } };
  1949.   { description = "Window", attrName = "window", attrType = "text", required = { attrName = "funcType", value = "switch" } };
  1950. }
  1951.  
  1952. Objects.Button.new = function(self, maxWidth, maxHeight)
  1953.   self.width = (maxWidth < Size.Button.width) and maxWidth or Size.Button.width
  1954.   self.height = (maxHeight < Size.Button.height) and maxHeight or Size.Button.height
  1955.   self.widthPercent = maxX / self.width
  1956.   self.heightPercent = maxY / self.height
  1957.   self.text = "Button"
  1958.   self.funcType = ""
  1959.   self.window = ""
  1960.   self.canScale = true
  1961.   self.canClick = true
  1962. end
  1963.  
  1964. Objects.Button.get = function(self, buttonColor)
  1965.   local width = self.width
  1966.   local height = self.height
  1967.   local path = self.path
  1968.   local text = self.text
  1969.   local color
  1970.  
  1971.   if buttonColor then
  1972.     color = buttonColor
  1973.   elseif (ObjectData.Button[self.objID]) then
  1974.     color = ObjectColors.Button.active
  1975.   else
  1976.     color = ObjectColors.Button.default
  1977.   end
  1978.  
  1979.   local buffer = Buffer:new()
  1980.   local textCol = math.floor((width - string.len(text)) / 2) + 1
  1981.   local textRow = math.ceil(height / 2)
  1982.   buffer:init(width, height, path, color, ObjectColors.Button.text)
  1983.   buffer:addText(textCol, textRow, text)
  1984.  
  1985.   return buffer
  1986. end
  1987.  
  1988. -- Returns: "button_clicked" event, objID
  1989. Objects.Button.click = function(self, x, y)
  1990.   assert(self)
  1991.  
  1992.   local objID = self.objID
  1993.   local funcType = self.funcType
  1994.   local window = self.window
  1995.   if (funcType == "switch") then
  1996.     drawWindow(window)
  1997.     return nil, nil
  1998.   elseif (funcType == "function" or funcType == "toggle function") then
  1999.     local state = (funcType == "function" or ObjectData.Button[self.objID] == true)
  2000.    
  2001.     if (funcType == "function" and changeButtonColor) then
  2002.       Objects.draw(self, ObjectColors.Button.active)
  2003.     elseif (funcType == "toggle function") then -- Required because changeButtonColor could be false
  2004.           state = not state
  2005.       ObjectData.Button[self.objID] = state
  2006.       Objects.draw(self)
  2007.     end
  2008.    
  2009.     if showCustomWindow and
  2010.         CustomFunctions[showCustomWindow] and
  2011.         CustomFunctions[showCustomWindow][objID] then
  2012.       CustomFunctions[showCustomWindow][objID](state)
  2013.     elseif (UserData[objID]) then
  2014.       UserData[objID](state)
  2015.     end
  2016.    
  2017.     if (funcType == "function") then
  2018.       if changeButtonColor then
  2019.         Objects.draw(self, ObjectColors.Button.default)
  2020.       else
  2021.         changeButtonColor = true
  2022.       end
  2023.     end
  2024.    
  2025.     local eventType = (funcType == "function") and "button_clicked" or "button_toggled"
  2026.    
  2027.     return eventType, {self.objID, state}
  2028.   else
  2029.     log("Unknown function type: \"" .. funcType .. "\"", "WARNING")
  2030.   end
  2031. end
  2032.  
  2033. -- >> Text
  2034.  
  2035. Objects.Text = {}
  2036. Objects.Text.attributes = {
  2037.   { description = "Text", attrName = "text", attrType = "text" };
  2038. }
  2039.  
  2040. Objects.Text.new = function(self)
  2041.   self.text = "newText"
  2042.   self.width = #self.text
  2043.   self.height = 1
  2044. end
  2045.  
  2046. Objects.Text.get = function(self)
  2047.   local width = #self.text
  2048.   local buffer = Buffer:new()
  2049.   buffer:init(width, 1, self.path)
  2050.   buffer:addText(1, 1, self.text)
  2051.  
  2052.   return buffer
  2053. end
  2054.  
  2055. Objects.Text.draw = function(self)
  2056.   local x = self.x
  2057.   local y = self.y
  2058.   local text = self.text
  2059.   out.setCursorPos(x, y)
  2060.   out.write(text)
  2061. end
  2062.  
  2063. Objects.Text.getDimensions = function(self)
  2064.   local left, top, right, bottom = self.x, self.y, 1, self.y
  2065.   right = left + string.len(self.text) - 1
  2066.   return left, top, right, bottom
  2067. end
  2068.  
  2069. Objects.Text.attributeChanged = function(self, key, oldValue, newValue)
  2070.   if (key == "text") then
  2071.     self.width = #newValue
  2072.   end
  2073. end
  2074.  
  2075. -- >> Variable
  2076.  
  2077. Objects.Variable = {}
  2078. Objects.Variable.attributes = {
  2079.   { description = "Object ID", attrName = "objID", attrType = "text" };
  2080.   { description = "Load Function", attrName = "load", attrType = "text" };
  2081. }
  2082.  
  2083. Objects.Variable.new = function(self)
  2084.   self.width = 1
  2085.   self.height = 1
  2086. end
  2087.  
  2088. Objects.Variable.get = function(self, value)
  2089.   local buffer = Buffer:new()
  2090.  
  2091.   if editMode and showCustomWindow ~= "Editor" then
  2092.     buffer:init(1, 1, self.path, ObjectColors.Editor.marker)
  2093.   elseif value then
  2094.     buffer:init(string.len(value), 1, self.path)
  2095.     buffer:addText(1, 1, value)
  2096.   else
  2097.     buffer:init(1, 1, self.path)
  2098.   end
  2099.  
  2100.   return buffer
  2101. end
  2102.  
  2103. -- >> ProgressBar
  2104.  
  2105. Objects.ProgressBar = {}
  2106. Objects.ProgressBar.attributes = {
  2107.   { description = "Object ID", attrName = "objID", attrType = "text" };
  2108.   { description = "Load Function", attrName = "load", attrType = "text" };
  2109. }
  2110.  
  2111. Objects.ProgressBar.new = function(self, maxWidth)
  2112.   self.length = (maxWidth < Size.ProgressBar.length) and maxWidth or Size.ProgressBar.length
  2113.   self.lengthPercent = maxX / self.length
  2114.   self.width = self.length
  2115.   self.height = 1
  2116.   self.direction = "right"
  2117.   self.objID = "newProgressBar"
  2118.   self.canScale = true
  2119. end
  2120.  
  2121. Objects.ProgressBar.get = function(self, value)
  2122.   local length = self.length
  2123.   local direction = (isValidDirection(self.direction)) and self.direction or "right"
  2124.   local orientation = getOrientation(direction) or error("Direction " .. direction .. " is invalid!")
  2125.   value = value or ProgressBarValues[self.objID]
  2126.  
  2127.   if (orientation == "horizontal") then
  2128.     width, height = length, 1
  2129.   else
  2130.     width, height = 1, length
  2131.   end
  2132.  
  2133.   local buffer = Buffer:new()
  2134.   buffer:init(width, height, self.path, ObjectColors.ProgressBar.background)
  2135.  
  2136.   if value then
  2137.     local color
  2138.     if (value < 33) then
  2139.       color = ObjectColors.ProgressBar.low
  2140.     elseif (value > 66) then
  2141.       color = ObjectColors.ProgressBar.high
  2142.     else
  2143.       color = ObjectColors.ProgressBar.medium
  2144.     end
  2145.    
  2146.     local filled = math.floor((length / 100) * value)
  2147.     local valueBuffer = Objects.Line.get(getOrientation(direction), filled, color)
  2148.     local addX, addY = 1, 1
  2149.    
  2150.     if (direction == "left") then
  2151.       addX = width - filled + 1
  2152.     elseif (direction == "up") then
  2153.       addY = height - filled + 1
  2154.     end
  2155.    
  2156.     buffer:addBuffer(addX, addY, valueBuffer)
  2157.   end
  2158.  
  2159.   return buffer
  2160. end
  2161.  
  2162. Objects.ProgressBar.getMovePos = function(self)
  2163.   local dir = self.direction
  2164.   local length = self.length
  2165.   local x, y = Objects.getAbsolutePos(self)
  2166.  
  2167.   if (dir == "left") then
  2168.     return x + length - 1, y
  2169.   elseif (dir == "up") then
  2170.     return x, y + length - 1
  2171.   else
  2172.     return x, y
  2173.   end
  2174. end
  2175.  
  2176. Objects.ProgressBar.getScalePos = function(self)
  2177.   local dir = self.direction
  2178.   local length = self.length
  2179.   local x, y = Objects.getAbsolutePos(self)
  2180.  
  2181.   if (dir == "right") then
  2182.     return x + length - 1, y
  2183.   elseif (dir == "down") then
  2184.     return x, y + length - 1
  2185.   else
  2186.     return x, y
  2187.   end
  2188. end
  2189.  
  2190. Objects.ProgressBar.scale = function(self, x, y)
  2191.   local moveX, moveY = Objects.getMovePos(self)
  2192.   local relMoveX, relMoveY = Path.getRelativePos(self.path, moveX, moveY)
  2193.   local length
  2194.   local direction
  2195.   local newX, newY
  2196.   local width, height
  2197.  
  2198.   if (x < relMoveX and y == relMoveY) then -- Clicked left of the progressBar.
  2199.     length = relMoveX - x + 1
  2200.     direction = "left"
  2201.     newX, newY = x, y
  2202.     width, height = length, 1
  2203.   elseif (x == relMoveX and y < relMoveY) then -- Clicked above the progressBar.
  2204.     length = relMoveY - y + 1
  2205.     direction = "up"
  2206.     newX, newY = x, y
  2207.     width, height = 1, length
  2208.   elseif (x > relMoveX and y == relMoveY) then -- Clicked right of the progressBar.
  2209.     length = x - relMoveX + 1
  2210.     direction = "right"
  2211.     newX, newY = relMoveX, relMoveY
  2212.     width, height = length, 1
  2213.   elseif (x == relMoveX and y > relMoveY) then -- Clicked below the progressBar.
  2214.     length = y - relMoveY + 1
  2215.     direction = "down"
  2216.     newX, newY = relMoveX, relMoveY
  2217.     width, height = 1, length
  2218.   else
  2219.     return
  2220.   end
  2221.  
  2222.   if (length > 2) then
  2223.     local xDiff, yDiff = newX - self.x, newY - self.y
  2224.    
  2225.     self.absoluteX, self.absoluteY = self.absoluteX + xDiff, self.absoluteY + yDiff
  2226.     self.x, self.y = newX, newY
  2227.     self.direction = direction
  2228.     self.width, self.height = width, height
  2229.     self.length = length
  2230.     self.lengthPercent = length / maxX
  2231.   end
  2232. end
  2233.  
  2234. -- >> Input
  2235.  
  2236. Objects.Input = {}
  2237. Objects.Input.attributes = {
  2238. { description = "Object ID", attrName = "objID", attrType = "text" };
  2239.   { description = "Message", attrName = "message", attrType = "text" };
  2240.   { description = "Is password", attrName = "isPassword", attrType = "bool" };
  2241. }
  2242.  
  2243. Objects.Input.new = function(self)
  2244.   self.message = "Enter something."
  2245.   self.isPassword = false
  2246.   self.width = 2
  2247.   self.height = 1
  2248.   self.canClick = true
  2249. end
  2250.  
  2251. Objects.Input.get = function(self)
  2252.   local userInput = ObjectData.Input[self.objID] or ""
  2253.   local width, height = 2 + string.len(userInput), 1
  2254.  
  2255.   local buffer = Buffer:new()
  2256.   buffer:init(width, height, self.path, ObjectColors.Input.default)
  2257.   if userInput ~= "" then
  2258.     buffer:addText(2, 1, userInput)
  2259.   end
  2260.  
  2261.   return buffer
  2262. end
  2263.  
  2264. -- Returns: "text_changed" event, objID, text
  2265. Objects.Input.click = function(self)
  2266.   local x = self.absoluteX
  2267.   local y = self.absoluteY
  2268.   local objID = self.objID
  2269.   local message = self.message
  2270.   local isPassword = (self.isPassword == nil) and false or self.isPassword
  2271.   local maxLength = self.maxLength
  2272.   local existingInput = ObjectData.Input[objID]
  2273.  
  2274.   out.setBackgroundColor(ObjectColors.background)
  2275.   out.setCursorPos(x, y)
  2276.   if (existingInput ~= nil) then -- Clear the text on the input object.
  2277.     out.write(string.rep(" ", string.len(existingInput) + 2))
  2278.   else
  2279.     out.write("  ")
  2280.   end
  2281.   ObjectData.Input[objID] = nil
  2282.  
  2283.   out.setCursorPos(x, y)
  2284.   if not outIsTerm then
  2285.     -- make the input-object yellow
  2286.     out.setBackgroundColor(ObjectColors["Input"].active)
  2287.     out.write("  ")
  2288.     out.setBackgroundColor(ObjectColors.background)
  2289.   end
  2290.  
  2291.   if outIsTerm then
  2292.     out.setCursorPos(x + 1, y)
  2293.   end
  2294.  
  2295.   local userInput = readUserInput(message, isPassword)
  2296.   if (userInput ~= nil) then
  2297.     ObjectData.Input[objID] = userInput
  2298.   end
  2299.  
  2300.   out.setCursorPos(x, y)
  2301.   out.setBackgroundColor(ObjectColors.Input.default)
  2302.   out.setTextColor(ObjectColors.Input.text)
  2303.  
  2304.   out.write(" ")
  2305.   if (userInput ~= nil and userInput ~= "") then
  2306.     if isPassword then
  2307.       for i = 1, string.len(userInput) do
  2308.         out.write("*")
  2309.       end
  2310.     else
  2311.       out.write(userInput)
  2312.     end
  2313.   end
  2314.  
  2315.   out.write(" ")
  2316.   out.setBackgroundColor(ObjectColors.background)
  2317.   out.setTextColor(ObjectColors.text)
  2318.  
  2319.   return "text_changed", {self.objID, userInput}
  2320. end
  2321.  
  2322. -- >> List
  2323.  
  2324. Objects.List = {}
  2325. Objects.List.attributes = {
  2326.   { description = "Object ID", attrName = "objID", attrType = "text" };
  2327.   { description = "Items", attrName = "Items", attrType = "table" };
  2328.   { description = "Is multiselect", attrName = "isMultiselect", attrType = "bool" };
  2329. }
  2330.  
  2331. Objects.List.new = function(self)
  2332.   self.Items = {}
  2333.   self.objID = "testList"
  2334.   self.isMultiselect = false
  2335.   self.canClick = true
  2336. end
  2337.  
  2338. Objects.List.get = function(self)
  2339.   assert(self)
  2340.  
  2341.   local buffer = Buffer:new()
  2342.  
  2343.   if self.load then
  2344.     self.Items = self.load()
  2345.   end
  2346.  
  2347.   self.Items = self.Items or { "empty" }
  2348.  
  2349.   if (#self.Items == 0) then
  2350.     self.Items = { "empty" }
  2351.   end
  2352.  
  2353.   self.width = getLongestString(self.Items) + 2
  2354.   self.height = #self.Items
  2355.  
  2356.   if not ObjectData.List[self.objID] then
  2357.     ObjectData.List[self.objID] = {}
  2358.   end
  2359.  
  2360.   buffer:init(self.width, self.height, self.path, ObjectColors.List.default)
  2361.  
  2362.   local line = 1
  2363.   for key, element in pairs(self.Items) do
  2364.     if (ObjectData.List[self.objID][key]) then
  2365.       buffer:addBuffer(1, line, Objects.Line.get("horizontal", self.width, ObjectColors.List.active))
  2366.     end
  2367.     buffer:addText(2, line, self.Items[line])
  2368.     line = line + 1
  2369.   end
  2370.  
  2371.   return buffer
  2372. end
  2373.  
  2374. -- Returns: "selection_changed" event, objID, key, true or false
  2375. Objects.List.click = function(self, x, y)
  2376.   local objID = self.objID
  2377.   local isMultiselect = self.isMultiselect
  2378.   local itemSelected = ObjectData.List[objID][y]
  2379.  
  2380.   if (isMultiselect) then
  2381.     ObjectData.List[objID][y] = not itemSelected
  2382.   else
  2383.     ObjectData.List[objID] = {}
  2384.     ObjectData.List[objID][y] = true
  2385.   end
  2386.  
  2387.   Objects.draw(self)
  2388.  
  2389.   return "selection_changed", {self.objID, y, ObjectData.List[objID][y]}
  2390. end
  2391.  
  2392. -- Same as List.click
  2393. Objects.List.drag = function(self, x, y)
  2394.   return Objects.List.click(self, x, y)
  2395. end
  2396.  
  2397. Objects.List.getFirstSelectedKey = function(self)
  2398.   assert(self)
  2399.  
  2400.   local objID = self.objID
  2401.  
  2402.   for key, value in pairs(ObjectData.List[objID]) do
  2403.     if (value == true and self.Items[key] ~= "empty") then
  2404.       return key
  2405.     end
  2406.   end
  2407.  
  2408.   return nil
  2409. end
  2410.  
  2411. Objects.List.getFirstSelectedValue = function(self)
  2412.   assert(self)
  2413.  
  2414.   key = Objects.List.getFirstSelectedKey(self)
  2415.  
  2416.   if key then
  2417.     return self.Items[key]
  2418.   else
  2419.     return nil
  2420.   end
  2421. end
  2422.  
  2423. -- >> FileSelector
  2424.  
  2425. Objects.FileSelector = {}
  2426. Objects.FileSelector.attributes = {
  2427.   { description = "Object ID", attrName = "objID", attrType = "text" };
  2428. }
  2429.  
  2430. Objects.FileSelector.new = function(self)
  2431.   self.width = string.len(Text.fileSelector)
  2432.   self.height = 1
  2433.   self.isMultiselect = false
  2434.   self.canClick = true
  2435. end
  2436.  
  2437. Objects.FileSelector.get = function(self)
  2438. error("not yet implemented")
  2439.   local objectID = objectID
  2440.   local x = self.x
  2441.   local y = self.y
  2442.   local isMultiselect = self.isMultiselect
  2443.  
  2444.   out.setBackgroundColor(ObjectColors["FileSelector"].default)
  2445.   out.setTextColor(ObjectColors["FileSelector"].text)
  2446.  
  2447.   out.setCursorPos(x, y)
  2448.   out.write(Text.fileSelector)
  2449.  
  2450.   if (ObjectData.FileSelector[objectID] ~= nil) then
  2451.     out.setBackgroundColor(ObjectColors.background)
  2452.     out.setTextColor(ObjectColors.text)
  2453.     local files = ObjectData.FileSelector[objectID]
  2454.     out.write(" ")
  2455.     if (type(files) == "table") then
  2456.       local sep = ""
  2457.       for _, fileName in pairs(files) do
  2458.         term.write(sep .. fileName)
  2459.         sep = ", "
  2460.       end
  2461.     else
  2462.       out.write(files)
  2463.     end
  2464.   end
  2465.  
  2466.   out.setBackgroundColor(ObjectColors.background)
  2467.   out.setTextColor(ObjectColors.text)
  2468. end
  2469.  
  2470. Objects.FileSelector.click = function(self, x, y)
  2471. error("Not yet implemented")
  2472. -- TODO
  2473.   local finished = false
  2474.   local path = "/"
  2475.   local list = {}
  2476.  
  2477.   while not finished do
  2478.     clearScreen()
  2479.     out.setCursorPos(2, 1)
  2480.     out.write("Path: " .. path)
  2481.    
  2482.     list = getFileList(path)
  2483.    
  2484.     out.setTextColor(ObjectColors.FileSelector.text)
  2485.   end
  2486. end
  2487.  
  2488. -- >> CheckBox
  2489. Objects.CheckBox = {}
  2490. Objects.CheckBox.attributes = {
  2491.   { description = "Object ID", attrName = "objID", attrType = "text" };
  2492.   { description = "Checked by default", attrName = "defaultIsChecked", attrType = "bool" };
  2493. }
  2494.  
  2495. Objects.CheckBox.new = function(self)
  2496.   self.objID = "newCheckBox"
  2497.   self.canClick = true
  2498.   self.canScale = false
  2499.   self.defaultIsChecked = false
  2500.   self.text = "new CheckBox"
  2501.   self.width = #self.text + 2
  2502.   self.height = 1
  2503. end
  2504.  
  2505. Objects.CheckBox.init = function(self)
  2506.   if (self.defaultIsChecked == true) then
  2507.     ObjectData.CheckBox[self.objID] = true
  2508.   end
  2509. end
  2510.  
  2511. Objects.CheckBox.get = function(self)
  2512.   if (ObjectData.CheckBox[self.objID] == nil) then
  2513.     ObjectData.CheckBox[self.objID] = self.defaultIsChecked
  2514.   end
  2515.  
  2516.   local buffer = Buffer:new()
  2517.   local isChecked = ObjectData.CheckBox[self.objID]
  2518.   local char = isChecked and "X" or " "
  2519.  
  2520.   buffer:init(self.width, self.height, self.path)
  2521.   buffer:setPixel(1, 1,
  2522.     { background = ObjectColors.CheckBox.default,
  2523.       text = ObjectColors.CheckBox.active,
  2524.       char = char })
  2525.   buffer:addText(3, 1, self.text)
  2526.  
  2527.   return buffer
  2528. end
  2529.  
  2530. -- returns "checked" event, object ID, checked state
  2531. Objects.CheckBox.click = function(self)
  2532.   ObjectData.CheckBox[self.objID] = not ObjectData.CheckBox[self.objID]
  2533.   Objects.draw(self)
  2534.  
  2535.   return "checked", { self.objID, ObjectData.CheckBox[self.objID] }
  2536. end
  2537.  
  2538. Objects.CheckBox.attributeChanged = function(self, key, oldValue, newValue)
  2539.   if (key == "text") then
  2540.     self.width = #newValue + 2
  2541.   end
  2542. end
  2543.  
  2544. -- >> RadioButton
  2545.  
  2546. Objects.RadioButton = {}
  2547. Objects.RadioButton.attributes = {
  2548.   { description = "Object ID", attrName = "objID", attrType = "text" };
  2549.   { description = "Text", attrName = "text", attrType = "text" };
  2550.   { description = "Group", attrName = "group", attrType = "text" };
  2551.   { description = "Checked by default", attrName = "defaultIsChecked", attrType = "bool" }
  2552. }
  2553.  
  2554. Objects.RadioButton.new = function(self)
  2555.   self.objID = "newRadioButton1"
  2556.   self.defaultIsChecked = false
  2557.   self.group = "default"
  2558.   self.text = "new RadioButton"
  2559.   self.width = #self.text + 2
  2560.   self.height = 1
  2561.   self.canClick = true
  2562.   self.canScale = false
  2563. end
  2564.  
  2565. Objects.RadioButton.init = function(self)
  2566.   if (self.defaultIsChecked == true) then
  2567.     ObjectData.RadioButton[self.objID] = true
  2568.   end
  2569. end
  2570.  
  2571. Objects.RadioButton.get = function(self)
  2572.   local buffer = Buffer:new()
  2573.   local color
  2574.  
  2575.   if ObjectData.RadioButton[self.objID] and not editMode then
  2576.     color = ObjectColors.RadioButton.active
  2577.   else
  2578.     color = ObjectColors.RadioButton.default
  2579.   end
  2580.  
  2581.   buffer:init(#self.text + 2, 1, self.path)
  2582.   buffer:setPixel(1, 1, { background = color, char = " "})
  2583.   buffer:addText(3, 1, self.text)
  2584.  
  2585.   return buffer
  2586. end
  2587.  
  2588. Objects.RadioButton.update = function(container, groupName, clickedID)
  2589.   for _, object in pairs(container.Children) do
  2590.     if (type(object) == "table") then
  2591.       if (object.objType == "RadioButton" and object.group == groupName) then
  2592.         ObjectData.RadioButton[object.objID] = (clickedID == object.objID)
  2593.         Objects.draw(object)
  2594.       elseif (object.isContainer) then
  2595.         Objects.RadioButton.update(object, groupName, clickedID)
  2596.       end
  2597.     end
  2598.   end
  2599. end
  2600.  
  2601. -- returns "radio_changed" event, object ID
  2602. Objects.RadioButton.click = function(self)
  2603.   Objects.RadioButton.update(getCurrentWindow(), self.group, self.objID)
  2604.  
  2605.   return "selection_changed", { self.objID }
  2606. end
  2607.  
  2608. Objects.RadioButton.attributeChanged = function(self, key, oldValue, newValue)
  2609.   if (key == "text") then
  2610.     self.with = #newValue + 2
  2611.   end
  2612. end
  2613.  
  2614. -- >> Slider
  2615.  
  2616. Objects.Slider = {}
  2617. Objects.Slider.attributes = {
  2618.   { description = "Object ID", attrName = "objID", attrType = "text" };
  2619.   { description = "Minimum value", attrName = "minValue", attrType = "number" };
  2620.   { description = "Maximum value (affects length)", attrName = "maxValue", attrType = "number" };
  2621. }
  2622.  
  2623. Objects.Slider.new = function(self)
  2624.   self.objID = "newSlider"
  2625.   self.width = Size.Slider.length
  2626.   self.height = 1
  2627.   self.minValue = 0
  2628.   self.maxValue = Size.Slider.length - 1
  2629.   self.canClick = true
  2630.   self.canScale = false
  2631. end
  2632.  
  2633. Objects.Slider.init = function(self)
  2634.   ObjectData.Slider[self.objID] = self.minValue
  2635. end
  2636.  
  2637. Objects.Slider.get = function(self)
  2638.   local buffer = Buffer:new()
  2639.  
  2640.   buffer:init(self.width, self.height, self.path, ObjectColors.Slider.default, ObjectColors.Slider.text)
  2641.   buffer:addText(1, 1, "|" .. string.rep("-", self.width - 2) .. "|")
  2642.  
  2643.   if ObjectData.Slider[self.objID] and not editMode then
  2644.     local pixel = { background = ObjectColors.Slider.active, text = ObjectColors.Slider.activeText, char = "|" }
  2645.     buffer:setPixel(ObjectData.Slider[self.objID] - self.minValue + 1, 1, pixel)
  2646.   end
  2647.  
  2648.   return buffer
  2649. end
  2650.  
  2651. Objects.Slider.attributeChanged = function(self, key, oldValue, newValue)
  2652.   if ((key == "minValue" or key == "manValue") and newValue % 1 ~= 0) then
  2653.     self[key] = round(newValue)
  2654.   end
  2655.  
  2656.   if (key == "minValue") then
  2657.     if (newValue >= self.maxValue) then
  2658.       self[key] = oldValue
  2659.     else -- adapt slider width
  2660.       self.width = self.maxValue - self.minValue + 1
  2661.     end
  2662.   elseif (key == "maxValue") then
  2663.     if (newValue <= self.minValue) then
  2664.       self[key] = oldValue
  2665.     else -- adapt slider width
  2666.       self.width = self.maxValue - self.minValue + 1
  2667.     end
  2668.   end
  2669. end
  2670.  
  2671. -- returns "slider_changed" event, object ID, value
  2672. Objects.Slider.click = function(self, x, y)
  2673.   ObjectData.Slider[self.objID] = x + self.minValue - 1
  2674.   Objects.draw(self)
  2675.  
  2676.   return "slider_changed", { self.objID, ObjectData.Slider[self.objID] }
  2677. end
  2678.  
  2679. Objects.Slider.drag = function(self, x, y)
  2680.   return Objects.Slider.click(self, x, y)
  2681. end
  2682.  
  2683. -- >> DropDownList
  2684.  
  2685. Objects.DropDownList = {}
  2686. Objects.DropDownList.attributes = {
  2687.   { description = "Object ID", attrName = "objID", attrType = "text" };
  2688.   { description = "Items", attrName = "Items", attrType = "table" };
  2689.   { description = "Load Function", attrName = "load", attrType = "text" };
  2690. }
  2691.  
  2692. Objects.DropDownList.new = function(self, maxWidth)
  2693.   self.Items = { "empty" }
  2694.   self.objID = "newDropDownList"
  2695.   self.width = (Size.DropDownList.width > maxWidth) and maxWidth or Size.DropDownList.width
  2696.   self.height = 1
  2697.   self.canClick = true
  2698.   self.canScale = true
  2699. end
  2700.  
  2701. Objects.DropDownList.get = function(self)
  2702.   assert(self)
  2703.  
  2704.   if self.load then
  2705.     self.Items = self.load()
  2706.   elseif not self.Items then
  2707.     self.Items = { "empty" }
  2708.   end
  2709.  
  2710.   local buffer = Buffer:new()
  2711.   buffer:init(self.width, 1, self.path, ObjectColors.DropDownList.default, ObjectColors.DropDownList.text)
  2712.  
  2713.   if ObjectData.DropDownList[self.objID] then
  2714.     buffer:addText(1, 1, self.Items[ObjectData.DropDownList[self.objID]])
  2715.   elseif #self.Items > 0 then
  2716.     ObjectData.DropDownList[self.objID] = 1
  2717.     buffer:addText(1, 1, self.Items[ObjectData.DropDownList[self.objID]])
  2718.   end
  2719.  
  2720.   buffer:addText(self.width - 2, 1, " V")
  2721.  
  2722.   return buffer
  2723. end
  2724.  
  2725. -- Returns: "selection_changed" event, objID, key
  2726. Objects.DropDownList.click = function(self, x, y)
  2727.   assert(self)
  2728.  
  2729.   local selectedItem = ObjectData.DropDownList[self.objID]
  2730.   local drawY
  2731.   local height = #self.Items
  2732.  
  2733.   -- Determine whether the list should be displayed below or above the object.
  2734.  
  2735.   if height >= maxX then -- Not enough space vertically.
  2736.     drawY = 1
  2737.   elseif maxX - height > self.absoluteY then
  2738.     drawY = self.absoluteY + 1
  2739.   elseif self.absoluteY - height > 0 then
  2740.     drawY = self.absoluteY - height
  2741.   else
  2742.     drawY = maxY - height
  2743.   end
  2744.  
  2745.   out.setTextColor(ObjectColors.DropDownList.text)
  2746.  
  2747.   for index = 1, height do
  2748.     if (index == ObjectData.DropDownList[self.objID]) then
  2749.       out.setBackgroundColor(ObjectColors.DropDownList.active)
  2750.     else
  2751.       out.setBackgroundColor(ObjectColors.DropDownList.default)
  2752.     end
  2753.    
  2754.     out.setCursorPos(self.absoluteX, drawY + index - 1)
  2755.     out.write(self.Items[index] .. string.rep(" ", self.width - #self.Items[index] - 1))
  2756.   end
  2757.  
  2758.   out.setBackgroundColor(ObjectColors.background)
  2759.   out.setTextColor(ObjectColors.text)
  2760.  
  2761.   local x, y, mouseButton = getCursorInput()
  2762.  
  2763.   if (x >= self.absoluteX and
  2764.       x <= self.absoluteX + self.width - 1 and
  2765.       y >= self.absoluteY + 1 and
  2766.       y <= self.absoluteY + height) then
  2767.     local selectedIndex = y - self.absoluteY
  2768.    
  2769.     ObjectData.DropDownList[self.objID] = selectedIndex
  2770.   end
  2771.  
  2772.   drawWindow()
  2773.  
  2774.   return "selection_changed", { self.objID, ObjectData.DropDownList[self.objID] }
  2775. end
  2776.  
  2777. Objects.DropDownList.scale = function(self, x, y)
  2778.   assert(self)
  2779.   assert(x)
  2780.   assert(y)
  2781.  
  2782.   local length = x - self.x + 1
  2783.  
  2784.   if (length >= 3) then
  2785.     self.width = length
  2786.   end
  2787. end
  2788.  
  2789. -- >>> Containers
  2790.  
  2791. Objects.Container = {}
  2792.  
  2793. Objects.Container.getNextFreeKey = function(self)
  2794.   local nextKey = 1
  2795.  
  2796.   while self.Children[nextKey] ~= nil do
  2797.     nextKey = nextKey + 1
  2798.   end
  2799.  
  2800.   return nextKey
  2801. end
  2802.  
  2803. -- Returns the area of the container which stores
  2804. -- its children.
  2805. Objects.Container.getContentArea = function(self)
  2806.   assert(self)
  2807.   local objType = self.objType
  2808.  
  2809.   if (Objects.Container[objType].getContentArea) then
  2810.     return Objects.Container[objType].getContentArea(self)
  2811.   else
  2812.     local left = self.x + 1
  2813.     local top = self.y + 1
  2814.     local right = self.x + self.width - 2
  2815.     local bottom = self.y + self.height - 2
  2816.     return left, top, right, bottom
  2817.   end
  2818. end
  2819.  
  2820. -- Determines whether the container itself or its
  2821. -- content area is at the given position.
  2822. Objects.Container.contentAreaClicked = function(self, x, y)
  2823.   local left, top, right, bottom = Objects.Container.getContentArea(self)    
  2824.  
  2825.   return (x >= left and x <= right and y >= top and y <= bottom)
  2826. end
  2827.  
  2828. -- Draws the buffer and trims it before that if
  2829. -- necessary.
  2830. Objects.Container.drawBuffer = function(self, buffer, path, x, y, absoluteX, absoluteY, nestLevel)
  2831.   --log("Objects.Container.drawBuffer", "FUNC")
  2832.   local modX, modY = Objects.Container.getPosModifier(self, x, y)
  2833.   x, y = x + modX, y + modY
  2834.  
  2835.   if (self.objType ~= "Window") then
  2836.     local containerLeft, containerTop, containerRight, containerBottom = 1, 1, self.width - 1, self.height - 1
  2837.     local objLeft, objTop, objRight, objBottom = x, y, x + buffer.width - 1, y + buffer.height - 1
  2838.    
  2839.     --log("Container dimensions: left: " .. containerLeft .. ", top: " .. containerTop .. ", right: " .. containerRight .. ", bottom: " ..containerBottom .. ".", "DEBUG")
  2840.     --log("Object dimensions: left: " .. objLeft .. ", top: " .. objTop .. ", right: " .. objRight .. ", bottom: " ..objBottom .. ".", "DEBUG")
  2841.    
  2842.     if (objLeft > self.width or
  2843.         objTop > self.height or
  2844.         objRight < 0 or
  2845.         objBottom < 0) then
  2846.       return
  2847.     elseif (objLeft < containerLeft or objTop < containerTop or objRight >= containerRight or objBottom >= containerBottom) then
  2848.       -- Object goes over the border. Trim it.
  2849.       local trimLeft = (containerLeft - objLeft) > 0 and containerLeft - objLeft or 0
  2850.       local trimTop = (containerTop - objTop) > 0 and containerTop - objTop or 0
  2851.       local trimRight = (objRight - containerRight + 1) > 0 and objRight - containerRight + 1 or 0
  2852.       local trimBottom = (objBottom - containerBottom + 1) > 0 and objBottom - containerBottom + 1 or 0
  2853.       buffer = buffer:trim(trimLeft, trimTop, trimRight, trimBottom)
  2854.      
  2855.       if (trimLeft > 0) then
  2856.         x = x + trimLeft
  2857.         absoluteX = absoluteX + trimLeft
  2858.       end
  2859.       if (trimTop > 0) then
  2860.         y = y + trimTop
  2861.         absoluteY = absoluteY + trimTop
  2862.       end
  2863.     end
  2864.   end
  2865.  
  2866.   if (nestLevel < #path - 1) then
  2867.     local container = Path.getContainerAt(path, nestLevel + 1)
  2868.     local relX, relY = Objects.Container.getRelativePos(container, x, y)
  2869.     Objects.Container.drawBuffer(container, buffer, path, relX, relY, absoluteX, absoluteY, nestLevel + 1)
  2870.   else
  2871.     buffer:draw(absoluteX + modX, absoluteY + modY)
  2872.   end
  2873. end
  2874.  
  2875. Objects.Container.getRelativePos = function(self, x, y)
  2876.   local left, top, right, bottom = Objects.Container.getContentArea(self)
  2877.  
  2878.   --log("Objects.Container.getRelativePos", "FUNC")
  2879.   --log(self.objType .. " ID: " .. self.objID .. ", x: " .. x .. ", y: " .. y .. ".", "ATTR")
  2880.   --log("Left: " .. left .. ", top: " .. top .. ", right: " .. right .. ", bottom: " .. bottom .. ".", "DEBUG")
  2881.  
  2882.   retX = x - left + 1
  2883.   retY = y - top + 1
  2884.  
  2885.   return retX, retY
  2886. end
  2887.  
  2888. Objects.Container.getParentsRelativePos = function(self, x, y)
  2889.   local left, top, right, bottom = Objects.Container.getContentArea(self)
  2890.  
  2891.   return x + left - 1, y + top - 1
  2892. end
  2893.  
  2894. Objects.Container.new = function(self, maxWidth, maxHeight)
  2895.   --log("Objects.Container.new", "FUNC")
  2896.   local defaultWidth, defaultHeight = Size.Container.width, Size.Container.height
  2897.  
  2898.   self.isContainer = true
  2899.   self.canScale = true
  2900.   self.Children = {}
  2901.   self.width = (maxWidth < defaultWidth) and maxWidth or defaultWidth
  2902.   self.height = (maxHeight < defaultHeight) and maxHeight or defaultHeight
  2903.  
  2904.   --log("New container dimensioins: " .. self.width .. " x " .. self.height .. ".")
  2905.  
  2906.   if (Objects.Container[self.objType].new) then
  2907.     Objects.Container[self.objType].new(self)
  2908.   end
  2909. end
  2910.  
  2911. Objects.Container.init = function(self)
  2912.   assert(self)
  2913.  
  2914.   local objType = self.objType
  2915.  
  2916.   if (Objects.Container[objType] and Objects.Container[objType].init) then
  2917.     Objects.Container[objType].init()
  2918.   end
  2919.  
  2920.   for _, object in pairs(self.Children) do
  2921.     if (type(object) == "table") then
  2922.       local objType = object.objType
  2923.      
  2924.       if (Objects[objType] and Objects[objType].init) then
  2925.         log("Initializting " .. objType .. " " .. object.objID .. ".", "INFO")
  2926.         Objects[objType].init(object)
  2927.       elseif (object.isContainer) then
  2928.         Objects.Container.init(object)
  2929.       end
  2930.     end
  2931.   end
  2932. end
  2933.  
  2934. Objects.Container.get = function(self)
  2935.   assert(self)
  2936.  
  2937.   local left, top, right, bottom = Objects.Container.getContentArea(self)
  2938.   local minWidth, minHeight = right - left + 1, bottom - top + 1
  2939.   local width, height = getNecessaryBufferSize(self.Children, minWidth, minHeight)
  2940.  
  2941.   local buffer = Buffer:new()
  2942.   buffer:init(width, height, {})
  2943.  
  2944.   for objectID, object in pairs(self.Children) do
  2945.     local objectBuffer = Objects.get(object)
  2946.     buffer:addBuffer(object.x, object.y, objectBuffer)
  2947.   end
  2948.  
  2949.   return Objects.Container[self.objType].get(self, buffer)
  2950. end
  2951.  
  2952. Objects.Container.getPosModifier = function(self, x, y)
  2953.   assert(self)
  2954.  
  2955.   --log("Objects.Container.getPosModifier", "FUNC")
  2956.   --log("Object type: " .. tostring(self.objType) .. ", ID: " .. tostring(self.objID) .. ".", "INFO")
  2957.   --log("Position: " .. x .. ", " .. y .. ".", "INFO")
  2958.  
  2959.   local objType = self.objType
  2960.   if (Objects.Container[objType] and Objects.Container[objType].getPosModifier) then
  2961.     local newX, newY = Objects.Container[objType].getPosModifier(self, x, y)
  2962.     --log("Modifier: " .. newX .. ", " .. newY .. ".", "INFO")
  2963.     return newX, newY
  2964.   else
  2965.     --log("Position not modified.")
  2966.     return 0, 0
  2967.   end
  2968. end
  2969.  
  2970. Objects.Container.move = function(self, addX, addY)
  2971.   self.absoluteX = self.absoluteX + addX
  2972.   self.absoluteY = self.absoluteY + addY
  2973.  
  2974.   for _, child in pairs(self.Children) do
  2975.     if child.isContainer then
  2976.       Objects.Container.move(child, addX, addY)
  2977.     else
  2978.       child.absoluteX = child.absoluteX + addX
  2979.       child.absoluteY = child.absoluteY + addY
  2980.     end
  2981.   end
  2982. end
  2983.  
  2984. -- >> Window
  2985.  
  2986. Objects.Container.Window = {}
  2987. Objects.Container.Window.new = function(windowName)
  2988.   local object = {}
  2989.   object.objType = "Window"
  2990.   object.Children = {}
  2991.   object.width, object.height = maxX, maxY
  2992.  
  2993.   return object
  2994. end
  2995.  
  2996. Objects.Container.Window.create = function(windowName)
  2997.   assert(windowName)
  2998.  
  2999.   local windowContainer = getWindowContainer()
  3000.   local object = Objects.Container.Window.new(windowName)
  3001.  
  3002.   windowContainer.Children[windowName] = object
  3003.  
  3004.   log("Created window \"" .. windowName .. "\" for window container \"" .. tostring(showCustomWindow) .. "\".", "INFO")
  3005. end
  3006.  
  3007. Objects.Container.Window.get = function(self, contentBuffer)
  3008.   return contentBuffer
  3009. end
  3010.  
  3011. Objects.Container.Window.getContentArea = function(self)
  3012.   return 1, 1, maxX, maxY
  3013. end
  3014.  
  3015. -- >> Panel
  3016.  
  3017. Objects.Container.Panel = {}
  3018. Objects.Container.Panel.attributes = {}
  3019.  
  3020. Objects.Container.Panel.get = function(self, contentBuffer)
  3021.   --log("Objects.Container.Panel.get", "FUNC")
  3022.   local buffer = Buffer:new()
  3023.   buffer:init(self.width, self.height, self.path, ObjectColors.Container.Panel.border)
  3024.   buffer:addBuffer(2, 2, contentBuffer)
  3025.   buffer:addBuffer(self.width, 1, Objects.Line.get("vertical", self.height, ObjectColors.Container.Panel.border, self.path))
  3026.   buffer:addBuffer(1, self.height, Objects.Line.get("horizontal", self.width, ObjectColors.Container.Panel.border, self.path))
  3027.  
  3028.   return buffer
  3029. end
  3030.  
  3031. -- >> ScrollView
  3032.  
  3033. Objects.Container.ScrollView = {}
  3034. Objects.Container.ScrollView.attributes = {}
  3035.  
  3036. Objects.Container.ScrollView.new = function(self)
  3037.   self.scrollX = 0
  3038.   self.scrollY = 0
  3039.   self.maxScrollX = 0
  3040.   self.maxScrollY = 0
  3041.   self.scrollXEnabled = false
  3042.   self.scrollYEnabled = true
  3043. end
  3044.  
  3045. Objects.Container.ScrollView.get = function(self, contentBuffer)
  3046.   --log("Objects.Container.ScrollView.get", "FUNC")
  3047.   --log("ScrollView (ID: " .. tostring(self.objID) .. ")", "INFO")
  3048.  
  3049.   local buffer = Buffer:new()
  3050.   buffer:init(self.width, self.height, self.path, ObjectColors.Container.ScrollView.border)
  3051.   buffer:addBuffer(2 - self.scrollX, 2 - self.scrollY, contentBuffer)
  3052.   buffer:makeBorder(self.path, ObjectColors.Container.ScrollView.border)
  3053.  
  3054.   --local edgePixel = { background=ObjectColors.Container.ScrollView.border, path=self.path }
  3055.   --buffer:setPixel(self.width, 1, edgePixel)
  3056.   --buffer:setPixel(self.width, self.height, edgePixel)
  3057.   --buffer:setPixel(1, self.height, edgePixel)
  3058.  
  3059.   if (self.scrollXEnabled) then
  3060.     self.maxScrollX = contentBuffer.width - self.width + 1
  3061.    
  3062.     buffer:addBuffer(2, self.height, Objects.Line.get("horizontal", self.width - 2, ObjectColors.Container.ScrollView.scrollBackground))
  3063.    
  3064.     if (self.width > 4) then
  3065.       local scrollBarInfo = getScrollBarInfo(self.scrollX, self.width, contentBuffer.width)
  3066.       buffer:addBuffer(scrollBarInfo.pos + 3, self.height, Objects.Line.get("horizontal", scrollBarInfo.size, ObjectColors.Container.ScrollView.scrollForeground, self.path))
  3067.     end
  3068.    
  3069.     buffer:setPixel(2, self.height, { char="<", path=self.path })
  3070.     buffer:setPixel(self.width - 1, self.height, { char=">", path=self.path })
  3071.   else
  3072.     --buffer:addBuffer(2, self.height, Objects.Line.get("horizontal", self.width - 2, ObjectColors.Container.ScrollView.border, self.path))
  3073.   end
  3074.  
  3075.   if (self.scrollYEnabled) then
  3076.     self.maxScrollY = contentBuffer.height - self.height + 1
  3077.     buffer:addBuffer(self.width, 2, Objects.Line.get("vertical", self.height - 2, ObjectColors.Container.ScrollView.scrollBackground))
  3078.    
  3079.     if (self.height > 4) then
  3080.       local scrollBarInfo = getScrollBarInfo(self.scrollY, self.height, contentBuffer.height)
  3081.       buffer:addBuffer(self.width, scrollBarInfo.pos + 3, Objects.Line.get("vertical", scrollBarInfo.size, ObjectColors.Container.ScrollView.scrollForeground, self.path))
  3082.     end
  3083.    
  3084.     buffer:setPixel(self.width, 2, { char="^", path=self.path })
  3085.     buffer:setPixel(self.width, self.height - 1, { char="V", path=self.path })
  3086.   else
  3087.     --buffer:addBuffer(self.width, 2, Objects.Line.get("vertical", self.height - 2, ObjectColors.Container.ScrollView.border, self.path))
  3088.   end
  3089.  
  3090.   return buffer
  3091. end
  3092.  
  3093. Objects.Container.ScrollView.click = function(self, x, y)
  3094.   if (x == self.width and y == 2) then -- Up
  3095.     --log("Up")
  3096.     if (self.scrollY > 0) then
  3097.       self.scrollY = self.scrollY - 1
  3098.     end
  3099.   elseif (x == self.width and y == self.height - 1) then -- Down
  3100.     --log("Down")
  3101.     if (self.scrollY < self.maxScrollY) then
  3102.       self.scrollY = self.scrollY + 1
  3103.     end
  3104.   elseif (x == 2 and y == self.height) then -- Left
  3105.     --log("Left")
  3106.     if (self.scrollX > 0) then
  3107.       self.scrollX = self.scrollX - 1
  3108.     end
  3109.   elseif (x == self.width - 1 and y == self.height) then -- Right
  3110.     --log("Right")
  3111.     if (self.scrollX < self.maxScrollX) then
  3112.       self.scrollX = self.scrollX + 1
  3113.     end
  3114.   end
  3115.  
  3116.   WindowBuffer:addBuffer(self.absoluteX, self.absoluteY, Objects.get(self))
  3117.   Objects.draw(self)
  3118. end
  3119.  
  3120. Objects.Container.ScrollView.getPosModifier = function(self, x, y)
  3121.   return self.scrollX * -1, self.scrollY * -1
  3122. end
  3123.  
  3124. Objects.Container.ScrollView.addMarker = function(self, buffer)
  3125.  
  3126.   if not (self.scrollXEnabled and self.scrollYEnabled) then
  3127.     if (not self.scrollXEnabled and self.width > 2) then
  3128.       buffer:addBuffer(2, self.height, Objects.Line.get("horizontal", self.width - 2, ObjectColors.Editor.editMarker))
  3129.     end
  3130.    
  3131.     if (not self.scrollYEnabled and self.height > 2) then
  3132.       buffer:addBuffer(self.width, 2, Objects.Line.get("vertical", self.height - 2, ObjectColors.Editor.editMarker))
  3133.     end
  3134.   end
  3135. end
  3136.  
  3137. Objects.Container.ScrollView.editorClick = function(self, x, y)
  3138.   -- Check whether an arrow has been clicked.
  3139.   if (self.scrollYEnabled and x == self.width and y == 2) then -- Up
  3140.     --log("Up")
  3141.     if (self.scrollY > 0) then
  3142.       self.scrollY = self.scrollY - 1
  3143.     end
  3144.    
  3145.     return true
  3146.   elseif (self.scrollYEnabled and x == self.width and y == self.height - 1) then -- Down
  3147.     --log("Down")
  3148.     self.scrollY = self.scrollY + 1
  3149.     return true
  3150.   elseif (self.scrollXEnabled and x == 2 and y == self.height) then -- Left
  3151.     --log("Left")
  3152.     if (self.scrollX > 0) then
  3153.       self.scrollX = self.scrollX - 1
  3154.     end
  3155.    
  3156.     return true
  3157.   elseif (self.scrollXEnabled and x == self.width - 1 and y == self.height) then -- Right
  3158.     --log("Right")
  3159.     self.scrollX = self.scrollX + 1
  3160.     return true
  3161.   else -- Check whether the scrollBar has been clicked.
  3162.     if (x >= 1 and x <= self.width - 1 and y == self.height) then
  3163.       self.scrollXEnabled = not self.scrollXEnabled
  3164.       return true
  3165.     elseif (x == self.width and y > 1 and y < self.height - 1) then
  3166.       self.scrollYEnabled = not self.scrollYEnabled
  3167.       return true
  3168.     end
  3169.   end
  3170.  
  3171.   return false
  3172. end
  3173.  
  3174. -- >> Non-addable objects.
  3175.  
  3176. -- >> Line
  3177.  
  3178. Objects.Line = {}
  3179. Objects.Line.get = function(orientation, length, color, path)
  3180.   if (orientation ~= "horizontal" and orientation ~= "vertical") then
  3181.     orientation = getOrientation(orientation) or error("Orientation " .. orientation .. " is invalid!", 1)
  3182.   end
  3183.  
  3184.   assert(length)
  3185.   assert(color)
  3186.  
  3187.   local width, height
  3188.   if (orientation == "horizontal") then
  3189.     width, height = length, 1
  3190.   else
  3191.     width, height = 1, length
  3192.   end
  3193.  
  3194.   local buffer = Buffer:new()
  3195.   buffer:init(width, height, path, color)
  3196.  
  3197.   return buffer
  3198. end
  3199.  
  3200. -- >> Selector
  3201. Objects.Selector = {}
  3202. Objects.Selector.draw = function(x, y, items)
  3203.   assert(x)
  3204.   assert(y)
  3205.   assert(items)
  3206.  
  3207.   width = getLongestString(items) + 2
  3208.   height = #items -- Items + up and down
  3209.   itemCount = #items
  3210.   displayCount = itemCount
  3211.  
  3212.   enoughXSpace = true
  3213.   -- determine where the selector should actually be displayed
  3214.   if (width > maxX) then -- Not enough monitors horizontally?
  3215.     x = 1
  3216.     enoughXSpace = false
  3217.   elseif (maxX - x < width) then -- Not enough space to the right.
  3218.     if (x >= width) then -- Let's see if there is space to the left.
  3219.       x = x - width
  3220.     else -- No space? Check where you've got more space.
  3221.       if (maxX / 2) > x then -- More space to the left.
  3222.         x = maxX - width + 1
  3223.         enoughXSpace = false
  3224.       else -- More space to the right
  3225.         x = 1
  3226.         enoughXSpace = false
  3227.       end
  3228.     end
  3229.   else -- Enough space to the right.
  3230.     x = x + 1
  3231.   end
  3232.  
  3233.   if (height > maxY - y) then -- Not enough space from y to bottom.
  3234.     if ((maxY / 2) > y) then -- More space below y.
  3235.       if enoughXSpace then
  3236.         if (maxY < height) then -- Too big for the whole screen.
  3237.           y = 1
  3238.           displayCount = maxY
  3239.         else -- Enough space next to x and not too high.
  3240.           y = maxY - height
  3241.         end
  3242.       else -- Can't display it next to the selected point.
  3243.         y = y + 1
  3244.         displayCount = maxY - y + 1
  3245.       end
  3246.     else -- More space above y.
  3247.       if enoughXSpace then
  3248.         if (y < height) then -- Not enough space from top to y.
  3249.           if (maxY < height) then -- Too big for the whole screen.
  3250.             y = 1
  3251.             displayCount = maxY
  3252.           else -- Enough space next to x and not too high.
  3253.             y = 1
  3254.           end
  3255.         else -- Enough space from top to y.
  3256.           y = y - height + 1
  3257.         end
  3258.       else
  3259.         if (y < height) then -- Not enough space from top to y.
  3260.           if (maxY < height) then -- Too big for the whole screen.
  3261.             y = 1
  3262.             displayCount = maxY
  3263.           else -- Not enough space next to x but not too high.
  3264.             y = 1
  3265.             displayCount = y - 2
  3266.           end
  3267.         else -- Enough space from top to y.
  3268.           y = y - height
  3269.         end
  3270.       end
  3271.     end
  3272.   end
  3273.  
  3274.   out.setBackgroundColor(ObjectColors.background)
  3275.  
  3276.   local drawArrows = displayCount < height
  3277.  
  3278.   local start = 1
  3279.   local scroll = 0
  3280.   local right = x + width - 1
  3281.   local bottom = y + height - 1
  3282.   local finished = false
  3283.   local result
  3284.  
  3285.   if drawArrows then
  3286.     displayCount = displayCount - 2
  3287.     local middle = math.floor(width / 2)
  3288.    
  3289.     out.setCursorPos(x + middle, y)
  3290.     out.write("^")
  3291.     out.setCursorPos(x + middle, bottom)
  3292.     out.write("V")
  3293.    
  3294.     start = 2
  3295.   end
  3296.  
  3297.   while not finished do
  3298.     out.setTextColor(ObjectColors.Editor.selectorText)
  3299.    
  3300.     for row = start, displayCount do
  3301.       local color = (row % 2 == 0) and ObjectColors.Editor.selector1 or ObjectColors.Editor.selector2
  3302.       local text = items[row + scroll]
  3303.      
  3304.       out.setBackgroundColor(color)
  3305.       out.setCursorPos(x, y + row - 1)
  3306.       out.write(" " .. text .. string.rep(" ", width - #text - 1))
  3307.     end
  3308.    
  3309.     out.setBackgroundColor(ObjectColors.background)
  3310.    
  3311.     local touchX, touchY, mouseButton = getCursorInput()
  3312.    
  3313.     if (touchX < x or touchX > right or touchY < y or touchY > bottom) then
  3314.       selectedItem = nil
  3315.       result = false
  3316.       finished = true
  3317.     else -- User touched the selector.
  3318.       if showArrows then
  3319.         if (touchY == y) then -- up
  3320.           if (scroll > 0) then -- Check whether it is possible to scroll up.
  3321.             scroll = scroll - 1
  3322.           end
  3323.         elseif (touchY == bottom) then -- down
  3324.           if (displayCount < itemCount) then
  3325.             if (scroll < itemCount - displayCount) then
  3326.               scroll = scroll + 1
  3327.             end
  3328.           end
  3329.         end
  3330.       else
  3331.         selectedItem = items[touchY - y + scroll + 1]
  3332.         result = true
  3333.         finished = true
  3334.       end
  3335.     end
  3336.   end
  3337.  
  3338.   drawWindow()
  3339.   return result
  3340. end
  3341.  
  3342. -- >> API Functions
  3343.  
  3344. -- API function: Sets the value of all variables
  3345. -- with the given ID.
  3346. function setVariableValue(variableID, newVar)
  3347.   VariableValues[variableID] = newVar
  3348. end
  3349.  
  3350. -- API function: Sets the value of all progressBars
  3351. -- with the given ID.
  3352. function setProgressBarValue(objID, newVar)
  3353.   ProgressBarValues[objID] = newVar
  3354. end
  3355.  
  3356. -- >> User Input Functions
  3357.  
  3358. -- Gets any input of the user
  3359. -- (not from the environment)
  3360. function getAnyInput()
  3361.   local finished = false
  3362.   local event = {}
  3363.  
  3364.   while not finished do
  3365.     finished = true
  3366.     os.sleep(0)
  3367.    
  3368.     input = {os.pullEvent()}
  3369.     event.eventType = input[1]
  3370.     event.dragged = false
  3371.    
  3372.     if (event.eventType == "monitor_touch" and not outIsTerm) then
  3373.       event.eventType = "mouse"
  3374.       event.x = input[3]
  3375.       event.y = input[4]
  3376.       event.mouseButton = 1
  3377.     elseif (event.eventType == "mouse_click" and outIsTerm) then
  3378.       event.eventType = "mouse"
  3379.       event.x = input[3]
  3380.       event.y = input[4]
  3381.       event.mouseButton = input[2]
  3382.     elseif (event.eventType == "mouse_drag") then
  3383.       event.eventType = "mouse"
  3384.       event.x = input[3]
  3385.       event.y = input[4]
  3386.       event.mouseButton = input[2]
  3387.       event.dragged = true
  3388.     elseif (event.eventType == "key") then
  3389.       event.key = input[2]
  3390.     else
  3391.       finished = false
  3392.     end
  3393.   end
  3394.  
  3395.   return event
  3396. end
  3397.  
  3398. -- Returns where the user clicked and which button
  3399. -- he pressed (always 1 if it's a monitor).
  3400. function getCursorInput(includeDrag)
  3401.   local finished = false
  3402.   local dragged = false -- Determines whether the event is "mouse_click" or "mouse_drag"
  3403.  
  3404.   while not finished do
  3405.     event, param, x, y = os.pullEvent()
  3406.    
  3407.     if (event == "monitor_touch" and not outIsTerm) then
  3408.       mouseButton = 1
  3409.       finished = true
  3410.     elseif (event == "mouse_click" and outIsTerm) then
  3411.       mouseButton = param
  3412.       finished = true
  3413.     elseif (event == "mouse_drag" and includeDrag) then
  3414.       mouseButton = param
  3415.       dragged = true
  3416.       finished = true
  3417.     end
  3418.   end
  3419.  
  3420.   return x, y, mouseButton, dragged
  3421. end
  3422.  
  3423. -- Waits until any key gets pressed.
  3424. function getKeyInput()
  3425.   os.pullEvent("key")
  3426. end
  3427.  
  3428. function readUserInput(message, isPassword)
  3429.   if not outIsTerm then
  3430.     print(message)
  3431.   end
  3432.    
  3433.   if isPassword  then
  3434.     ret = read("*")
  3435.   else
  3436.     ret = read()
  3437.   end
  3438.  
  3439.   return ret
  3440. end
  3441.  
  3442. -- >> Display Functions
  3443.  
  3444. -- Has to be used instead of paintutils.drawpixel
  3445. function drawPixel(x, y, color)
  3446.   assert(x)
  3447.   assert(y)
  3448.   assert(color)
  3449.  
  3450.   out.setCursorPos(x, y)
  3451.   out.setBackgroundColor(color)
  3452.   out.write(" ")
  3453. end
  3454.  
  3455. function drawBox(x, y, width, height, color)
  3456.   out.setBackgroundColor(color)
  3457.  
  3458.   for row = 1, height do
  3459.     out.setCursorPos(x, y + row - 1)
  3460.     out.write(string.rep(" ", width))
  3461.    
  3462.     for col = x, width do
  3463.       if (WindowBuffer[col] and WindowBuffer[col][y + row - 1]) then
  3464.         WindowBuffer[col][y + row - 1].draw = true
  3465.       end
  3466.     end
  3467.   end
  3468. end
  3469.  
  3470. -- Displays the text with red background colour.
  3471. function drawSimpleButton(x, y, text, backgroundColor, textColor)
  3472.   assert(x)
  3473.   assert(y)
  3474.   assert(text)
  3475.  
  3476.   backgroundColor = backgroundColor or ObjectColors.Button.default
  3477.   textColor = textColor or ObjectColors.Button.text
  3478.  
  3479.   out.setCursorPos(x, y)
  3480.   out.setBackgroundColor(backgroundColor)
  3481.   out.setTextColor(textColor)
  3482.   out.write(text)
  3483.  
  3484.   out.setBackgroundColor(ObjectColors.background)
  3485.   out.setTextColor(ObjectColors.text)
  3486. end
  3487.  
  3488. -- Displays the default buttons.
  3489. function drawDefaultButtons()
  3490.   local window = getCurrentWindow()
  3491.   local button
  3492.  
  3493.   if (window.showRefreshButton) then
  3494.     button = DefaultButtons.refresh
  3495.     drawSimpleButton(button.left, button.top, button.text, ObjectColors.DefaultButtons.default, ObjectColors.DefaultButtons.text) -- Refresh
  3496.   end
  3497.  
  3498.   if (window.showBackButton) then
  3499.     button = DefaultButtons.back
  3500.     drawSimpleButton(button.left, button.top, button.text, ObjectColors.DefaultButtons.default, ObjectColors.DefaultButtons.text) -- Back
  3501.   end
  3502.  
  3503.   if (window.showQuitButton ~= false) then
  3504.     button = DefaultButtons.quit
  3505.     drawSimpleButton(button.left, button.top, button.text, ObjectColors.DefaultButtons.Quit.default, ObjectColors.DefaultButtons.Quit.text) -- Quit
  3506.   end
  3507.  
  3508.   button = DefaultButtons.options
  3509.   if (button.required()) then
  3510.     drawSimpleButton(button.left, button.top, button.text, ObjectColors.DefaultButtons.default, ObjectColors.DefaultButtons.text) -- Options
  3511.   end
  3512. end
  3513.  
  3514. -- Displays all objects of the window with the
  3515. -- ID windowID on the screen and changes the
  3516. -- variable "currentWindow".
  3517. function drawWindow(windowID)
  3518.   local windowContainer = getWindowContainer()
  3519.  
  3520.   if windowID and windowContainer and windowContainer.Children[windowID] then
  3521.     currentWindow = windowID
  3522.   elseif currentWindow then
  3523.     windowID = currentWindow
  3524.   elseif not showCustomWindow then
  3525.     if Windows.startupWindow then
  3526.       currentWindow = Windows.startupWindow
  3527.     elseif (#Windows.Children ~= 0) then -- Automatically set a startup window.
  3528.       for windowName, window in pairs(Windows.Children) do
  3529.         Windows.startupWindow = windowName
  3530.         currentWindow = windowName
  3531.         break
  3532.       end
  3533.     else -- We have no windows, create the "Main" window.
  3534.       Objects.Container.Window.create("Main")
  3535.       Windows.Children.Main.isStartupWindow = true
  3536.       currentWindow = "Main"
  3537.     end
  3538.   else
  3539.     error("No current window for custom window container " .. showCustomWindow .. " found.", 1)
  3540.   end
  3541.  
  3542.   log("Drawing window \"" .. currentWindow .. "\" with custom window \"" .. tostring(showCustomWindow) .. "\".", "INFO")
  3543.   local windowObject = getCurrentWindow()
  3544.  
  3545.   if windowObject then
  3546.     WindowBuffer = Objects.Container.get(windowObject)
  3547.     WindowBuffer:draw()
  3548.     drawDefaultButtons()
  3549.   end
  3550. end
  3551.  
  3552. -- >> Input Processing
  3553.  
  3554. -- Works just like os.pullEvent but it only
  3555. -- returns custom Graffiti events.
  3556. -- Recommended for API usage.
  3557. function pullEvent(requestedEvent)
  3558.   if not eventTypeExists(requestedEvent) then
  3559.     clearScreen()
  3560.     print("Event type " .. tostring(requestedEvent) .. " is invalid!")
  3561.     print()
  3562.     print("Available event types:")
  3563.    
  3564.     for _, event in pairs(EventTypes) do
  3565.       print("  " .. event)
  3566.     end
  3567.    
  3568.     error()
  3569.   end
  3570.  
  3571.   local finished = false
  3572.   local event, params
  3573.  
  3574.   while not finished do
  3575.     event, params = getInput()
  3576.    
  3577.     if event then
  3578.       if (requestedEvent ~= nil) then
  3579.         if (requestedEvent == event or event == "quit") then
  3580.           finished = true
  3581.         end
  3582.       else
  3583.         finished = true
  3584.       end
  3585.     end
  3586.   end
  3587.  
  3588.   return event, unpack(params)
  3589. end
  3590.  
  3591. function getInput()
  3592.   log("getInput", "FUNC")
  3593.  
  3594.   local finished = false
  3595.   local event, params
  3596.   local x, y, mouseButton, dragged = getCursorInput(true)
  3597.   log("Received mouse input. X: " .. x .. ", Y: " .. y .. ", button: " .. mouseButton .. ", dragged: " .. tostring(dragged) .. ".", "INFO")
  3598.  
  3599.   if not dragged then
  3600.     if (defaultButtonPressed("quit", x, y)) then
  3601.       log("Quit pressed")
  3602.       quit = true
  3603.     elseif (defaultButtonPressed("refresh", x, y)) then
  3604.       log("Refresh pressed")
  3605.       drawWindow()
  3606.       finished = true
  3607.     elseif (defaultButtonPressed("back", x, y)) then
  3608.       log("Back pressed")
  3609.      
  3610.       local windowObject = getCurrentWindow()
  3611.      
  3612.       if (windowObject.parent ~= nil) then
  3613.         drawWindow(windowObject.parent)
  3614.         finished = true
  3615.       else
  3616.         finished = true
  3617.       end
  3618.     end
  3619.   end
  3620.  
  3621.   if finished then
  3622.     return nil
  3623.   elseif quit then
  3624.     return "quit", { "Graffiti" } -- Used for the API
  3625.   end
  3626.  
  3627.   local param
  3628.   local path = WindowBuffer.bufferTable[x][y].path
  3629.  
  3630.   if path and #path > 0 then
  3631.     local object = Path.getObject(path)
  3632.     local clickX, clickY = Path.getRelativePos(path, x, y)
  3633.     local modX, modY = Objects.getPosModifier(object)
  3634.     clickX, clickY = clickX + (modX * -1), clickY + (modY * -1)
  3635.     clickX, clickY = clickX - object.x + 1, clickY - object.y + 1
  3636.    
  3637.     if dragged then
  3638.       event, params = Objects.drag(object, clickX, clickY)
  3639.     else
  3640.       event, params = Objects.click(object, clickX, clickY)
  3641.     end
  3642.   end
  3643.  
  3644.   return event, params
  3645. end
  3646.  
  3647. -- Shows the message on the computer for debugging.
  3648. function debugMessage(message)
  3649.   if outIsTerm then
  3650.     error("Can't display a debug message on a computer!")
  3651.   end
  3652.  
  3653.   print(message)
  3654. end
  3655.  
  3656. function splitAt(self, delimiter)
  3657.   delimiterPos = string.find(self, delimiter)
  3658.   left = string.sub(self, 1, delimiterPos - 1)
  3659.   right = string.sub(self, delimiterPos + #delimiter)
  3660.  
  3661.   return left, right
  3662. end
  3663.  
  3664. -- >>> Custom Windows
  3665.  
  3666. CustomWindows.init = function()
  3667.   log("Initializing custom windows...", "INFO")
  3668.  
  3669.   for windowName, window in pairs(CustomWindows) do
  3670.     if (type(window) == "table" and window.init) then
  3671.       log("Initializing custom window \"" .. windowName .. "\".", "INFO")
  3672.       window.init()
  3673.     end
  3674.   end
  3675.  
  3676.   showCustomWindow = nil
  3677.   currentWindow = nil
  3678. end
  3679.  
  3680. CustomWindows.draw = function(containerName, windowName)
  3681.   assert(containerName)
  3682.   assert(windowName)
  3683.  
  3684.   log("Drawing custom window \"" .. windowName .. "\" from container \"" .. containerName .. "\".", "INFO")
  3685.  
  3686.   showCustomWindow = containerName
  3687.   drawWindow(windowName)
  3688.   local result = nil
  3689.  
  3690.   while result == nil do
  3691.     local event, params = getInput()
  3692.    
  3693.     if (event == "button_clicked") then
  3694.       local objID = params[1]
  3695.      
  3696.       if (objID == "apply") then
  3697.         result = true
  3698.       elseif (objID == "cancel") then
  3699.         result = false
  3700.       end
  3701.     elseif (event == "quit") then
  3702.       result = true
  3703.       quit = false
  3704.     end
  3705.   end
  3706.  
  3707.   clearScreen()
  3708.   showCustomWindow = nil
  3709.  
  3710.   return result
  3711. end
  3712.  
  3713. -- >> Editor Windows
  3714. CustomWindows.Editor = {}
  3715.  
  3716. CustomWindows.Editor.init = function()
  3717.   showCustomWindow = "Editor"
  3718.   CustomWindows.Editor.Children = {}
  3719.  
  3720.   -- Main window
  3721.   Objects.Container.Window.create("Main")
  3722.   currentWindow = "Main"
  3723.  
  3724.   Objects.createCustom("ScrollView", 2, 2, { objID="windowListContainer", width=30, height=maxY-2, scrollXEnabled=true, scrollYEnabled=true } )
  3725.   Objects.createCustom("List", 4, 4, { objID="WindowList", items={}, load=CustomFunctions.Editor.getWindowList, isMultiselect=false } )
  3726.  
  3727.   Objects.createCustom("Panel", 34, 2, { objID="newWindow", width=15, height=7 } )
  3728.   Objects.createCustom("Input", 36, 4, { objID="newWindow", message="Enter a name for the new window." } )
  3729.   Objects.createCustom("Button", 36, 6, { objID="newWindow", width=8, height=1, text="New", funcType="function" } )
  3730.   Objects.createCustom("Button", 36, 7, { objID="renameWindow", width=8, height=1, text="Rename", funcType="function" } )
  3731.  
  3732.   Objects.createCustom("Button", 34, 10, { objID="editWindow", width=17, height=1, text="Edit", funcType="function" } )
  3733.   Objects.createCustom("Button", 34, 12, { objID="deleteWindow", width=17, height=1, text="Delete", funcType="function" } )
  3734.   Objects.createCustom("Button", 34, 14, { objID="setParent", width=17, height=1, text="Set parent", funcType="function" } )
  3735.   Objects.createCustom("Button", 34, 16, { objID="setStartup", width=17, height=1, text="Startup window", funcType="function" } )
  3736. end
  3737.  
  3738. CustomFunctions.Editor = {}
  3739.  
  3740. CustomFunctions.Editor.getWindowList = function()
  3741.   local ret = {}
  3742.  
  3743.   for key, value in pairs(Windows.Children) do
  3744.     table.insert(ret, key)
  3745.   end
  3746.  
  3747.   table.sort(ret)
  3748.  
  3749.   return ret
  3750. end
  3751.  
  3752. CustomFunctions.Editor.getWindowListObject = function()
  3753.   return CustomWindows.Editor.Children.Main.Children[1].Children[1]
  3754. end
  3755.  
  3756. CustomFunctions.Editor.editLastWindow = function()
  3757.  
  3758.   if not lastWindow then
  3759.     if Windows.startupWindow then
  3760.       showCustomWindow = nil
  3761.       lastWindow = Windows.startupWindow
  3762.     else
  3763.       return
  3764.     end
  3765.   end
  3766.  
  3767.   changeButtonColor = false
  3768.   drawWindow(lastWindow)
  3769. end
  3770.  
  3771. -- Creates a new window. The user has to enter the window name in the computer.
  3772. CustomFunctions.Editor.newWindow = function()
  3773.   local windowName = ObjectData.Input["newWindow"]
  3774.   local list = CustomFunctions.Editor.getWindowListObject()
  3775.  
  3776.   if (windowName == nil or windowName == "") then
  3777.     return
  3778.   elseif (Windows.Children[windowName] == nil) then
  3779.     showCustomWindow = nil
  3780.     Objects.Container.Window.create(windowName)
  3781.     changeButtonColor = false
  3782.     showCustomWindow = "Editor"
  3783.   end
  3784.  
  3785.   ListEditorList = list.Items
  3786.   ObjectData.Input["newWindow"] = ""
  3787.   drawWindow()
  3788. end
  3789.  
  3790. CustomFunctions.Editor.renameWindow = function()
  3791.   local newWindowName = ObjectData.Input["newWindow"]
  3792.   local list = CustomFunctions.Editor.getWindowListObject()
  3793.   local key = Objects.List.getFirstSelectedValue(list)
  3794.  
  3795.   if (value and not table.contains(windowList, newWindowName)) then
  3796.     local oldWindowName = value
  3797.    
  3798.     Files.rename({ Files.Project.subDir, currentProject }, oldWindowName .. Files.Project.extension, newWindowName .. Files.Project.extension)
  3799.     Windows.Children[newWindowName] = Windows.Children[oldWindowName]
  3800.     Windows.Children[oldWindowName] = nil
  3801.   end
  3802.  
  3803.   drawWindow()
  3804. end
  3805.  
  3806. -- Edits the first selected window.
  3807. CustomFunctions.Editor.editWindow = function()
  3808.   local list = CustomFunctions.Editor.getWindowListObject()
  3809.   local key = Objects.List.getFirstSelectedKey(list)
  3810.  
  3811.   if not key then
  3812.     return
  3813.   end
  3814.  
  3815.   showCustomWindow = nil
  3816.   lastWindow = list.Items[key]
  3817.   changeButtonColor = false
  3818.   drawWindow(lastWindow)
  3819. end
  3820.  
  3821. -- Deletes the selected window(s).
  3822. CustomFunctions.Editor.deleteWindow = function()
  3823.   local list = CustomFunctions.Editor.getWindowListObject()
  3824.  
  3825.   for key, selected in pairs(ObjectData.List[list.objID]) do
  3826.     if (selected == true) then
  3827.       local windowName = list.Items[key]
  3828.       Files.remove(Files.Project.subDir, fs.combine(currentProject, windowName .. Files.Project.extension))
  3829.       Windows.Children[windowName] = nil
  3830.     end
  3831.   end
  3832.  
  3833.   drawWindow()
  3834.   ListEditorList = list.Items
  3835. end
  3836.  
  3837. -- Sets the first selected window as the one that
  3838. -- should be displayed on startup.
  3839. CustomFunctions.Editor.setStartup = function()
  3840.   local list = CustomFunctions.Editor.getWindowListObject()
  3841.   local key = Objects.List.getFirstSelectedKey(list)
  3842.   local selectedWindowName = list.Items[key]
  3843.  
  3844.   for windowName, window in pairs(Windows.Children) do
  3845.     window.isStartupWindow = (windowName == selectedWindowName)
  3846.   end
  3847. end
  3848.  
  3849. -- Let's the user define the parent-attribute of the current window.
  3850. CustomFunctions.Editor.setParent = function()
  3851.   local list = CustomFunctions.Editor.getWindowListObject()
  3852.   local selected = Objects.List.getFirstSelectedKey(list)
  3853.   local buffer = Objects.Line.get("vertical", list.height, ObjectColors.background, {1, 99})
  3854.  
  3855.   local x, y, mouseButton = getCursorInput()
  3856.   local modX, modY = Objects.getPosModifier(list)
  3857.   x, y = x + (modX * -1), y + (modY * -1)
  3858.   local selectedParent = y - list.absoluteY + 1
  3859.  
  3860.   if (selectedParent >= 1 and selectedParent <= list.height) then -- Clicked inside the list.
  3861.     if (selectedParent ~= selected) then -- Selected parentWindow is not selected window.
  3862.       Windows.Children[list.Items[selected]].parent = list.Items[selectedParent]
  3863.     end
  3864.   end
  3865.  
  3866.   for i = 1, list.height do
  3867.     drawPixel(1, i + list.absoluteY - 1, ObjectColors.background)
  3868.   end
  3869. end
  3870.  
  3871. -- >> List Editor Windows
  3872. CustomWindows.ListEditor = {}
  3873.  
  3874. CustomWindows.ListEditor.init = function()
  3875.   showCustomWindow = "ListEditor"
  3876.   CustomWindows.ListEditor.Children = {}
  3877.  
  3878.   Objects.Container.Window.create("Main")
  3879.   CustomWindows.ListEditor.Children["Main"].showQuitButton = false
  3880.   currentWindow = "Main"
  3881.  
  3882.   Objects.createCustom("ScrollView", 2, 2, { objID="listContainer", width=22, height=maxY - 3, scrollXEnabled=true, scrollYEnabled=true } )
  3883.   Objects.createCustom("List", 4, 4, { objID="itemList", isMultiselect=true, items={}, load=CustomFunctions.ListEditor.getListEditorItems } )
  3884.  
  3885.   Objects.createCustom("Panel", 26, 2, { objID="newEntryPanel", width=16, height=7 } )
  3886.   Objects.createCustom("Input", 28, 4, { objID="newEntry" } )
  3887.   Objects.createCustom("Button", 28, 6, { objID="newEntry", width=11, height=1, text="New", funcType="function" } )
  3888.   Objects.createCustom("Button", 28, 7, { objID="renameEntry", width=11, height=1, text="Rename", funcType="function" } )
  3889.  
  3890.   Objects.createCustom("Text", 26, 11, { objID="editEntryText",   text="Edit entries" } )
  3891.   Objects.createCustom("Panel", 26, 12, { objID="editEntryPanel", width=16, height=8 } )
  3892.   Objects.createCustom("Button", 28, 14, { objID="moveEntryUp",   width=11, height=1, text="Move up", funcType="function" } )
  3893.   Objects.createCustom("Button", 28, 15, { objID="moveEntryDown", width=11, height=1, text="Move down", funcType="function" } )
  3894.   Objects.createCustom("Button", 28, 17, { objID="removeEntry",   width=11, height=1, text="Remove", funcType="function" } )
  3895.  
  3896.   Objects.createCustom("Button", 43, 12, { objID="apply", width=8, height=3, text="OK", funcType="function" } )
  3897.   Objects.createCustom("Button", 43, 16, { objID="cancel", width=8, height=3, text="Cancel", funcType="function" } )
  3898. end
  3899.  
  3900. CustomFunctions.ListEditor = {}
  3901.  
  3902. CustomFunctions.ListEditor.getListEditorItems = function()
  3903.   local tempList = {}
  3904.  
  3905.   for _, value in ipairs(ListEditorList) do
  3906.     table.insert(tempList, value)
  3907.   end
  3908.  
  3909.   return tempList
  3910. end
  3911.  
  3912. CustomFunctions.ListEditor.newEntry = function()
  3913.   local input = ObjectData.Input["newEntry"]
  3914.  
  3915.   if (input == nil or input == "") then
  3916.     return
  3917.   end
  3918.  
  3919.   table.insert(ListEditorList, input)
  3920.   ObjectData.Input["newEntry"] = nil
  3921.   drawWindow()
  3922. end
  3923.  
  3924. CustomFunctions.ListEditor.renameEntry = function()
  3925.   local selectedItems = ObjectData.List["itemList"]
  3926.   local input = ObjectData.Input["newEntry"]
  3927.  
  3928.   if (input == nil or input == "") then
  3929.     return
  3930.   end
  3931.  
  3932.   for key, value in pairs(selectedItems) do
  3933.     if value then -- Item is selected
  3934.       ListEditorList[key] = input
  3935.     end
  3936.   end
  3937.  
  3938.   ObjectData.Input["newEntry"] = nil
  3939.   drawWindow()
  3940. end
  3941.  
  3942. CustomFunctions.ListEditor.moveEntryUp = function()
  3943.   if not ObjectData.List["itemList"] then
  3944.     return
  3945.   end
  3946.  
  3947.   local lastIndex = nil
  3948.   local selectedItems = ObjectData.List["itemList"]
  3949.   local lock = nil
  3950.  
  3951.   for key, value in pairs(ListEditorList) do
  3952.     local selected = selectedItems[key] == true
  3953.    
  3954.     if lastIndex then
  3955.       if selected then
  3956.         if (lock ~= lastIndex) then
  3957.           selectedItems[lastIndex], selectedItems[key] =
  3958.               selectedItems[key], selectedItems[lastIndex]
  3959.          
  3960.           ListEditorList[lastIndex], ListEditorList[key] =
  3961.               ListEditorList[key], ListEditorList[lastIndex]
  3962.         else
  3963.           lock = key
  3964.         end
  3965.       end
  3966.     elseif selected then
  3967.       lock = key
  3968.     end
  3969.    
  3970.     lastIndex = key
  3971.   end
  3972.  
  3973.   drawWindow()
  3974. end
  3975.  
  3976. CustomFunctions.ListEditor.moveEntryDown = function()
  3977.   if not ObjectData.List["itemList"] then
  3978.     return
  3979.   end
  3980.  
  3981.   local lastIndex = nil
  3982.   local selectedItems = ObjectData.List["itemList"]
  3983.   local lock = nil
  3984.  
  3985.   for key = #ListEditorList, 1, -1 do
  3986.     local selected = selectedItems[key] == true
  3987.    
  3988.     if lastIndex then
  3989.       if selected then
  3990.         if (lock ~= lastIndex) then
  3991.           selectedItems[lastIndex], selectedItems[key] =
  3992.               selectedItems[key], selectedItems[lastIndex]
  3993.          
  3994.           ListEditorList[lastIndex], ListEditorList[key] =
  3995.               ListEditorList[key], ListEditorList[lastIndex]
  3996.         else
  3997.           lock = key
  3998.         end
  3999.       end
  4000.     elseif selected then
  4001.       lock = key
  4002.     end
  4003.    
  4004.     lastIndex = key
  4005.   end
  4006.  
  4007.   drawWindow()
  4008. end
  4009.  
  4010. CustomFunctions.ListEditor.removeEntry = function()
  4011.   local selectedItems = ObjectData.List["itemList"]
  4012.   local ret = {}
  4013.  
  4014.   for key, value in pairs(ListEditorList) do
  4015.     local selected = (selectedItems[key] == true)
  4016.    
  4017.     if not selected then
  4018.       table.insert(ret, ListEditorList[key])
  4019.     end
  4020.   end
  4021.  
  4022.   ObjectData.List["itemList"] = {}
  4023.   ListEditorList = ret
  4024.  
  4025.   drawWindow()
  4026. end
  4027.  
  4028. -- >> Setup Windows
  4029. CustomWindows.Setup = {}
  4030.  
  4031. CustomWindows.Setup.init = function()
  4032.   showCustomWindow = "Setup"
  4033.   CustomWindows.Setup.Children = {}
  4034.  
  4035.   Objects.Container.Window.create("Main")
  4036.   currentWindow = "Main"
  4037.  
  4038.   Objects.createCustom("Text", 2, 2, { objID="welcomeText", text=Text.setupText1 } )
  4039.   Objects.createCustom("Text", 2, 3, { objID="welcomeText", text=Text.setupText2 } )
  4040.   Objects.createCustom("Text", 2, 5, { objID="welcomeText", text=Text.setupText3 } )
  4041.   Objects.createCustom("Text", 2, 6, { objID="welcomeText", text=Text.setupText4 } )
  4042.   Objects.createCustom("Text", 2, 7, { objID="welcomeText", text=Text.setupText5 } )
  4043.  
  4044.   Objects.createCustom("Button", 2, 9, { objID="nextWindow", text=Text.next, width=6, height=3, funcType="switch", window="Setup1" } )
  4045.   Objects.createCustom("Button", 2, 13, { objID="cancel", text=Text.skipSetup, width=36, height=3, funcType="function" } )
  4046.  
  4047.   Objects.Container.Window.create("Setup1")
  4048.   CustomWindows.Setup.Children["Setup1"].showBackButton = true
  4049.   CustomWindows.Setup.Children["Setup1"].parent = "Main"
  4050.   currentWindow = "Setup1"
  4051.  
  4052.   Objects.createCustom("Text", 2, 2, { objID="chooseSettings", text=Text.chooseSettings1 } )
  4053.  
  4054.   Objects.createCustom("Text", 2, 4, { objID="chooseLanguage", text=Text.language .. ":" } )
  4055.   Objects.createCustom("ScrollView", 2, 5, { objID="Language", width=14, height=maxY - 6, scrollXEnabled=true, scrollYEnabled=true } )
  4056.   CustomWindows.Setup.languageList = Objects.createCustom("List", 4, 7, { objID="languages", load=CustomFunctions.Setup.getLanguageList } )
  4057.  
  4058.   Objects.createCustom("Text", 17, 4, { objID="chooseColorTheme", text=Text.colorTheme .. ":" } )
  4059.   Objects.createCustom("ScrollView", 17, 5, { objID="colorTheme", width=14, height=maxY - 6, scrollXEnabled=true, scrollYEnabled=true } )
  4060.   CustomWindows.Setup.colorThemeList = Objects.createCustom("List", 19, 7, { objID="colorTheme", load=CustomFunctions.Setup.getColorThemeList } )
  4061.  
  4062.   Objects.createCustom("RadioButton", 34, 4, { objID="showDataFolder", text=Text.showDataFolder, defaultIsChecked=true } )
  4063.   Objects.createCustom("RadioButton", 34, 6, { objID="hideDataFolder", text=Text.hideDataFolder } )
  4064.  
  4065.   Objects.createCustom("Button", 34, 16, { objID="apply", text=Text.finished, width=15, height=3, funcType="function" } )
  4066. end
  4067.  
  4068. CustomFunctions.Setup = {}
  4069.  
  4070. CustomFunctions.Setup.getLanguageList = function()
  4071.   local ret = {}
  4072.  
  4073.   for key, value in pairs(Languages) do
  4074.     table.insert(ret, key)
  4075.   end
  4076.  
  4077.   table.sort(ret)
  4078.  
  4079.   return ret
  4080. end
  4081.  
  4082. CustomFunctions.Setup.getColorThemeList = function()
  4083.   local ret = {}
  4084.  
  4085.   for key, value in pairs(ColorThemes) do
  4086.     table.insert(ret, key)
  4087.   end
  4088.  
  4089.   table.sort(ret)
  4090.  
  4091.   return ret
  4092. end
  4093.  
  4094. -- Runs the setup.
  4095. function runSetup(useDefaultSettings)
  4096.   if not useDefaultSettings then
  4097.     initDefaultButtons()
  4098.     CustomWindows.Setup.init()
  4099.     CustomWindows.draw("Setup", "Main")
  4100.   end
  4101.  
  4102.   local languageListObject = CustomWindows.Setup.languageList
  4103.   local colorThemeListObject = CustomWindows.Setup.colorThemeList
  4104.  
  4105.   local selectedLanguage = Objects.List.getFirstSelectedValue(languageListObject)
  4106.   local selectedColorTheme = Objects.List.getFirstSelectedValue(colorThemeListObject)
  4107.  
  4108.   if selectedLanguage and Languages[selectedLanguage] then
  4109.     Settings.language = selectedLanguage
  4110.   end
  4111.  
  4112.   if (selectedColorTheme and ColorThemes[selectedColorTheme]) then
  4113.     Settings.colorTheme = selectedColorTheme
  4114.   end
  4115.  
  4116.   Settings.hideDataFolder = ObjectData.RadioButton["hideDataFolder"] == true
  4117.  
  4118.   dataFolderPath = fs.combine(root, (Settings.hideDataFolder and ".GraffitiData" or "GraffitiData"))
  4119.  
  4120.   if not fs.exists(dataFolderPath) then
  4121.     fs.makeDir(dataFolderPath)
  4122.   end
  4123.  
  4124.   Files.save()
  4125. end
  4126.  
  4127. -- Shows lines marking the top left part of an
  4128. -- object as well as well as pixels displaying
  4129. -- the alignment of an object.
  4130. function drawAlignmentLines(object, left, top, right, bottom)
  4131.   local color = ObjectColors.Editor.marker
  4132.   local moveX, moveY = Objects.getMovePos(object)
  4133.  
  4134.   -- Draw the lines.
  4135.   Objects.Line.draw(left - 1, moveY, "left", left - 2, color) -- left
  4136.   Objects.Line.draw(moveX, top -1, "up", top - 2, color) -- up
  4137.   Objects.Line.draw(right + 1, moveY, "right", maxX - (right + 1), color) -- right
  4138.   Objects.Line.draw(moveX, bottom + 1, "down", maxY - (bottom + 1), color) -- down
  4139.  
  4140.   -- Display the alignment-pixels.
  4141.   horizontalAlignment = object.horizontalAlignment
  4142.   verticalAlignment = object.verticalAlignment
  4143.  
  4144.   if (horizontalAlignment == "left" or horizontalAlignment == "stretch") then -- left
  4145.     drawPixel(1, moveY, ObjectColors["Editor"].alignmentTrue)
  4146.   else
  4147.     drawPixel(1, moveY, ObjectColors["Editor"].alignmentFalse)
  4148.   end
  4149.  
  4150.   if (horizontalAlignment == "right" or horizontalAlignment == "stretch") then -- right
  4151.     drawPixel(maxX, moveY, ObjectColors["Editor"].alignmentTrue)
  4152.   else
  4153.     drawPixel(maxX, moveY, ObjectColors["Editor"].alignmentFalse)
  4154.   end
  4155.  
  4156.   if (verticalAlignment == "top" or verticalAlignment == "stretch") then -- top
  4157.     drawPixel(moveX, 1, ObjectColors["Editor"].alignmentTrue)
  4158.   else
  4159.     drawPixel(moveX, 1, ObjectColors["Editor"].alignmentFalse)
  4160.   end
  4161.  
  4162.   if (verticalAlignment == "bottom" or verticalAlignment == "stretch") then -- bottom
  4163.     drawPixel(moveX, maxY, ObjectColors["Editor"].alignmentTrue)
  4164.   else
  4165.     drawPixel(moveX, maxY, ObjectColors["Editor"].alignmentFalse)
  4166.   end
  4167.  
  4168.   out.setBackgroundColor(ObjectColors.background)
  4169. end
  4170.  
  4171. -- Returns the values of horizontalAlignment and
  4172. -- verticalAlignment depending which sides are set
  4173. -- to true.
  4174. function getAlignment(left, top, right, bottom)
  4175.   local retHorizontal, retVertical = "left", "top"
  4176.  
  4177.   if right then
  4178.     if left then
  4179.       retHorizontal = "stretch"
  4180.     else
  4181.       retHorizontal = "right"
  4182.     end
  4183.   else
  4184.     retHorizontal = "left"
  4185.   end
  4186.  
  4187.   if bottom then
  4188.     if top then
  4189.       retVertical = "stretch"
  4190.     else
  4191.       retVertical = "bottom"
  4192.     end
  4193.   else
  4194.     retVertical = "top"
  4195.   end
  4196.  
  4197.   return retHorizontal, retVertical
  4198. end
  4199.  
  4200. function markSelectedObject()
  4201.   if not selectedObject then
  4202.     return
  4203.   end
  4204.  
  4205.   Objects.draw(selectedObject, nil, true) -- Draw the object with its markers.
  4206.  
  4207.   local moveX, moveY = Objects.getMovePos(selectedObject)
  4208.   drawPixel(moveX, moveY, (currentEditorAction == "Move" and not selectedObjectDragged) and ObjectColors.Editor.active or ObjectColors.Editor.move)
  4209.  
  4210.   if selectedObject.canScale then
  4211.     local scaleX, scaleY = Objects.getScalePos(selectedObject)
  4212.     drawPixel(scaleX, scaleY, (currentEditorAction == "Scale" and not selectedObjectDragged) and ObjectColors.Editor.active or ObjectColors.Editor.scale)
  4213.   end
  4214.  
  4215.   out.setBackgroundColor(ObjectColors.background)
  4216. end
  4217.  
  4218. -- Let's the user delete an object or change its attributes depending on the current edit-mode.
  4219. function editObject(object, x, y, dragged)
  4220.   assert(object)
  4221.   log("Editing " .. object.objType .. ", ID: " .. object.objID .. ", x: " .. x .. ", y: " .. y .. ", dragged: " .. tostring(dragged) .. ".")
  4222.  
  4223.   local relX, relY = Path.getRelativePos(object.path, x, y)
  4224.   local modX, modY = Objects.getPosModifier(object)
  4225.   local left, top, right, bottom = Objects.getDimensions(object)
  4226.   left, top, right, bottom = left + modX, top + modY, right + modX, bottom + modY
  4227.  
  4228.   local moveX, moveY = Objects.getMovePos(object)
  4229.  
  4230.   local scaleX, scaleY
  4231.   if object.canScale then
  4232.     scaleX, scaleY = Objects.getScalePos(object)
  4233.   end
  4234.  
  4235.   if currentEditorAction then
  4236.     if (currentEditorAction == "Move") then
  4237.       addX = x - moveX
  4238.       addY = y - moveY
  4239.       Objects.move(object, addX, addY)
  4240.     elseif (currentEditorAction == "Scale") then
  4241.       Objects.scale(object, relX + (modX * -1), relY + (modY * -1))
  4242.     else
  4243.       error("Unknown editor action \"" .. tostring(currentEditorAction) .. "\".", 1)
  4244.     end
  4245.    
  4246.     if dragged then
  4247.       selectedObjectDragged = true
  4248.     else
  4249.       currentEditorAction = nil
  4250.     end
  4251.   else
  4252.     if (x == moveX and y == moveY) then
  4253.       currentEditorAction = "Move"
  4254.     elseif (object.canScale and x == scaleX and y == scaleY) then
  4255.       currentEditorAction = "Scale"
  4256.     else
  4257.       relX, relY = relX - object.x + 1, relY - object.y + 1
  4258.      
  4259.       if not (Objects.editorClick(object, relX + modX, relY + modY)) then
  4260.         if (not outIsTerm and Objects.Selector.draw(x, y, RightClickActions)) then
  4261.           if (selectedItem == "Attributes") then
  4262.             Objects.editAttributes(object)
  4263.           elseif (selectedItem == "Delete") then
  4264.             Objects.remove(object, "Delete")
  4265.             drawWindow()
  4266.           end
  4267.         end
  4268.       end
  4269.     end
  4270.   end
  4271.  
  4272.   out.setBackgroundColor(ObjectColors.background)
  4273.   drawWindow()
  4274. end
  4275.  
  4276. function markVariables(container)
  4277.   assert(container)
  4278.  
  4279.   for _, object in pairs(container.Children) do
  4280.     if (object.isContainer) then
  4281.       markVariables(object)
  4282.     elseif (object.objType == "Variable") then
  4283.       local x, y = Objects.getAbsolutePos(object)
  4284.       drawPixel(x, y, ObjectColors.Editor.marker)
  4285.       out.setBackgroundColor(ObjectColors.background)
  4286.     end
  4287.   end
  4288. end
  4289.  
  4290. function markDefaultButtons()
  4291.   local window = getCurrentWindow()
  4292.  
  4293.   -- refresh button
  4294.   local refresh = DefaultButtons.refresh
  4295.   out.setCursorPos(refresh.left, refresh.top)
  4296.   if (window.showRefreshButton) then
  4297.     out.setBackgroundColor(ObjectColors.Button.default)
  4298.     out.write(refresh.text)
  4299.   else
  4300.     out.setBackgroundColor(ObjectColors.Editor.marker)
  4301.     out.write(string.rep(" ", #refresh.text))
  4302.   end
  4303.  
  4304.   -- back button
  4305.   local back = DefaultButtons.back
  4306.   out.setCursorPos(back.left, back.top)
  4307.   if (window.showBackButton) then
  4308.     out.setBackgroundColor(ObjectColors.Button.default)
  4309.     out.write(back.text)
  4310.   else
  4311.     out.setBackgroundColor(ObjectColors.Editor.marker)
  4312.     out.write(string.rep(" ", #back.text))
  4313.   end
  4314.  
  4315.   out.setBackgroundColor(ObjectColors.background)
  4316. end
  4317.  
  4318. function getEditorInput()
  4319.   log("getEditorInput", "FUNC")
  4320.  
  4321.   local event
  4322.   local x, y, mouseButton, dragged
  4323.  
  4324.   if showCustomWindow ~= "Editor" then
  4325.     drawWindow()
  4326.     markDefaultButtons()
  4327.     markSelectedObject()
  4328.    
  4329.     event = getAnyInput()
  4330.    
  4331.     if (event.eventType == "mouse") then
  4332.       x, y, mouseButton, dragged = event.x, event.y, event.mouseButton, event.dragged
  4333.       log("Received mouse input. X: " .. x .. ", Y: " .. y .. ", button: " .. mouseButton .. ".")
  4334.     end
  4335.   end
  4336.  
  4337.   if (showCustomWindow ~= "Editor" and event.eventType == "key") then
  4338.     callShortcut(event.key)
  4339.   elseif (showCustomWindow == "Editor" or defaultButtonPressed("options", x, y)) then
  4340.     selectedObject = nil
  4341.     showCustomWindow = "Editor"
  4342.     drawWindow("Main")
  4343.    
  4344.     while showCustomWindow == "Editor" and not quit do
  4345.       getInput()
  4346.     end
  4347.   elseif (not dragged and defaultButtonPressed("quit", x, y)) then
  4348.     quit = true
  4349.   elseif (not dragged and defaultButtonPressed("refresh", x, y)) then
  4350.     Windows.Children[currentWindow].showRefreshButton = not Windows.Children[currentWindow].showRefreshButton
  4351.   elseif (not dragged and defaultButtonPressed("back", x, y)) then
  4352.     Windows.Children[currentWindow].showBackButton = not Windows.Children[currentWindow].showBackButton
  4353.   else
  4354.     if dragged then
  4355.       if (selectedObject == nil) then
  4356.         return
  4357.       else
  4358.         editObject(selectedObject, x, y, dragged)
  4359.       end
  4360.     else
  4361.       if selectedObjectDragged then
  4362.         selectedObjectDragged = false
  4363.         currentEditorAction = nil
  4364.       end
  4365.      
  4366.       local container = getCurrentWindow()
  4367.       local path = WindowBuffer.bufferTable[x][y].path
  4368.      
  4369.       if (path == nil or #path == 0) then -- No object touched.
  4370.         if selectedObject then
  4371.           if currentEditorAction then
  4372.             editObject(selectedObject, x, y)
  4373.           end
  4374.          
  4375.           selectedObject = nil
  4376.         else
  4377.           drawPixel(x, y, ObjectColors.Editor.new)
  4378.           if (Objects.Selector.draw(x, y, ObjectTypes)) then -- Draw selector for new object.
  4379.             Objects.create(selectedItem, x, y)
  4380.           end
  4381.         end
  4382.       else
  4383.         local object = Path.getObject(path)
  4384.        
  4385.         if (mouseButton == 1) then
  4386.           if (selectedObject and Objects.isClicked(selectedObject, x, y)) then
  4387.             editObject(selectedObject, x, y, dragged)
  4388.           else
  4389.             selectedObject = object
  4390.           end
  4391.         else
  4392.           if selectedObject then
  4393.             selectedObject = nil
  4394.           elseif (Objects.Selector.draw(x, y, RightClickActions)) then
  4395.             if (selectedItem == "Attributes") then
  4396.               Objects.editAttributes(object)
  4397.             elseif (selectedItem == "Delete") then
  4398.               Objects.remove(object, "Delete")
  4399.             end
  4400.           end
  4401.         end
  4402.       end
  4403.     end
  4404.   end
  4405. end
  4406.  
  4407. -- Runs Graffiti in editMode.
  4408. function windowEditor()
  4409.   editMode = true
  4410.  
  4411.   showCustomWindow = "Editor"
  4412.  
  4413.   while not quit do
  4414.     getEditorInput()
  4415.   end
  4416. end
  4417.  
  4418. function round(number)
  4419.   assert(number)
  4420.   comma = number % 1
  4421.   if comma < 0.5 then
  4422.     ret = math.floor(number)
  4423.   else
  4424.     ret = math.ceil(number)
  4425.   end
  4426.  
  4427.   return ret
  4428. end
  4429.  
  4430. function printInfo()
  4431.   print()
  4432.   print(version)
  4433.   print("Author: Encreedem")
  4434.   print()
  4435.   print("Param(s):")
  4436.   print("info - Shows some info about the program... but I guess you know that already.")
  4437.   print("edit - Starts the program in edit-mode.")
  4438.   print()
  4439.   print("Visit the CC-forums or my YouTube channel (Encreedem CP) for news and help.")
  4440. end
  4441.  
  4442. -- Gets called when Graffiti gets the argument "test"
  4443. function testMethod()
  4444.   error("Nothing to test...", 2)
  4445. end
  4446.  
  4447. -- >>> initialization
  4448.  
  4449. -- Sets the "startupWindow" variable to the first
  4450. -- window with enabled "isStartupWindow" attribute.
  4451. function getStartupWindow()
  4452.   for windowName, window in pairs(Windows.Children) do
  4453.     if window.isStartupWindow then
  4454.       startupWindow = windowName
  4455.       return
  4456.     end
  4457.   end
  4458. end
  4459.  
  4460. -- Initializes the default buttons.
  4461. -- (Quit, Back, Refresh, Options)
  4462. function initDefaultButtons()
  4463.   DefaultButtons.quit = {
  4464.     text=Text.quit,
  4465.     left=maxX - string.len(Text.quit) + 1,
  4466.     top=1,
  4467.     right=maxX,
  4468.     bottom=1,
  4469.     required = function()
  4470.       return getCurrentWindow().showQuitButton ~= false
  4471.     end
  4472.   }
  4473.  
  4474.   DefaultButtons.back = {
  4475.     text = Text.back,
  4476.     left = 1,
  4477.     top = 1,
  4478.     right = string.len(Text.back),
  4479.     bottom = 1,
  4480.     required = function()
  4481.       return getCurrentWindow().showBackButton
  4482.     end
  4483.   }
  4484.  
  4485.   DefaultButtons.refresh = {
  4486.     text = Text.refresh,
  4487.     left = maxX - string.len(Text.refresh) + 1,
  4488.     top = maxY,
  4489.     right = maxX,
  4490.     bottom = maxY,
  4491.     required = function()
  4492.       return (getCurrentWindow().showRefreshButton or (editMode and not showEdtorOptions))
  4493.     end
  4494.   }
  4495.  
  4496.   DefaultButtons.options = {
  4497.     text = Text.options,
  4498.     left = 1,
  4499.     top = maxY,
  4500.     right = string.len(Text.options),
  4501.     bottom = maxY,
  4502.     required=function()
  4503.       return (editMode and showCustomWindow ~= "Editor")
  4504.     end
  4505.   }
  4506. end
  4507.  
  4508. -- Initializes all windows to allow Graffiti to
  4509. -- use them as if they were objects.
  4510. function initWindows()
  4511.   for _, window in pairs(Windows.Children) do
  4512.     window.width, window.height = maxX, maxY
  4513.   end
  4514. end
  4515.  
  4516. -- Tells the user that the monitor or computer
  4517. -- doesn't support colors.
  4518. function showColorWarning()
  4519.   out.clear()
  4520.   out.setCursorPos(2, 2)
  4521.   out.write("This computer/monitor does not support colors!")
  4522.  
  4523.   local state = 0
  4524.   local move = "I don't know this move!"
  4525.   local finished = false
  4526.   while not finished and not quit do
  4527.     out.setCursorPos(1, 4)
  4528.     out.clearLine()
  4529.     out.setCursorPos(2, 4)
  4530.    
  4531.     if (state == 0) then
  4532.       move = "<( \" <) <( \" <) <( \" <)"
  4533.     elseif (state == 1 or state == 3 or state == 5) then
  4534.       move = "  (^\"^)   (^\"^)   (^\"^)"
  4535.     elseif (state == 2) then
  4536.       move = "  (> \" )> (> \" )> (> \" )>"
  4537.     elseif (state == 4) then
  4538.       move = " (> \" )><( \" )><( \" <)"
  4539.     elseif (state == 6) then
  4540.       move = "<( \" <) (>\"<) (> \" )>"
  4541.     elseif (state == 7) then
  4542.       move = " (v''v) (v''v) (v''v)"
  4543.     else
  4544.       error("Unable to show you that you need an advanced computer/monitor in a fancy way!")
  4545.     end
  4546.    
  4547.     out.write(move)
  4548.     state = (state + 1) % 8
  4549.     os.sleep(0.25)
  4550.   end
  4551. end
  4552.  
  4553. -- Checks if the monitor on monitorSide exists and wraps it into "monitor".
  4554. function getOutput()
  4555.   if (monitor == nil and outIsTerm == false) then
  4556.     local monitorFound = false
  4557.     for _, side in pairs(Sides) do
  4558.       if (peripheral.getType(side) == "monitor") then
  4559.         monitor = peripheral.wrap(side)
  4560.         monitorFound = true
  4561.         out = monitor
  4562.         outIsTerm = false
  4563.       end
  4564.     end
  4565.    
  4566.     if not monitorFound then
  4567.       out = term
  4568.       outIsTerm = true
  4569.     end
  4570.   elseif outIsTerm then
  4571.     out = term
  4572.   else
  4573.     out = monitor
  4574.   end
  4575. end
  4576.  
  4577. -- Initializes Graffitis' variables.
  4578. function init()
  4579.   getOutput()
  4580.  
  4581.   maxX, maxY = out.getSize()
  4582.   if (maxX < 16 or maxY < 10) then -- smaller than 2x2
  4583.     print("Screen too small! You need at least 2x2 monitors!")
  4584.     return false
  4585.   elseif not out.isColor() then
  4586.     parallel.waitForAny(showColorWarning, getKeyInput)
  4587.     clearScreen()
  4588.     return false
  4589.   end
  4590.  
  4591.   isAPI = (shell == nil)
  4592.  
  4593.   initDone = true
  4594.   return true
  4595. end
  4596.  
  4597. function checkArgs()
  4598.   doCall = main
  4599.   arg = Args[1]
  4600.  
  4601.   if (arg ~= nil) then
  4602.     if (arg == "edit") then
  4603.       doCall = windowEditor
  4604.     elseif (arg == "info") then
  4605.       doCall = printInfo
  4606.     elseif (arg == "term") then
  4607.       outIsTerm = true
  4608.     elseif (arg == "test") then
  4609.       doCall = testMethod
  4610.     end
  4611.   end
  4612.  
  4613.   doCall()
  4614. end
  4615.  
  4616. -- Calls the "getInput" function until the user presses the quit-button.
  4617. function main()
  4618.   drawWindow(startupWindow)
  4619.  
  4620.   while not quit do
  4621.     getInput()
  4622.   end
  4623. end
  4624.  
  4625. if init() then
  4626.   Files.init()
  4627.   Files.clear(Files.Log.subDir, Files.Log.name)
  4628.   logFileLoaded = true
  4629.   log("Graffiti initialized.")
  4630.  
  4631.   getStartupWindow()
  4632.   initWindows()
  4633.   CustomWindows.init()
  4634.   initDefaultButtons()
  4635.   Objects.init()
  4636.  
  4637.   if not isAPI then
  4638.     checkArgs() -- Calls the "main" function. Everything below happens after closing Graffiti.
  4639.    
  4640.     -- Closing Program
  4641.     if editMode and saveAfterQuit then
  4642.       Files.save()
  4643.     end
  4644.    
  4645.     out.setBackgroundColor(colors.black)
  4646.     out.setTextColor(colors.white)
  4647.     out.clear()
  4648.     out.setCursorPos(1, 1)
  4649.   else
  4650.     log("Graffiti loaded as an API.")
  4651.   end
  4652. else
  4653.   error("Graffiti Initialization failed!")
  4654. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement