Advertisement
Encreedem

Graffiti v1.7.1

Mar 7th, 2014
274
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 133.36 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.1"
  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. local 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. local Objects = {}
  263. local 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 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.       ObjectData.Button[self.objID] = not state
  2005.       Objects.draw(self)
  2006.     end
  2007.    
  2008.     if showCustomWindow and
  2009.         CustomFunctions[showCustomWindow] and
  2010.         CustomFunctions[showCustomWindow][objID] then
  2011.       CustomFunctions[showCustomWindow][objID](state)
  2012.     elseif (UserData[objID]) then
  2013.       UserData[objID](state)
  2014.     end
  2015.    
  2016.     if (funcType == "function") then
  2017.       if changeButtonColor then
  2018.         Objects.draw(self, ObjectColors.Button.default)
  2019.       else
  2020.         changeButtonColor = true
  2021.       end
  2022.     end
  2023.    
  2024.     local eventType = (funcType == "function") and "button_clicked" or "button_toggled"
  2025.    
  2026.     return eventType, {self.objID, state}
  2027.   else
  2028.     log("Unknown function type: \"" .. funcType .. "\"", "WARNING")
  2029.   end
  2030. end
  2031.  
  2032. -- >> Text
  2033.  
  2034. Objects.Text = {}
  2035. Objects.Text.attributes = {
  2036.   { description = "Text", attrName = "text", attrType = "text" };
  2037. }
  2038.  
  2039. Objects.Text.new = function(self)
  2040.   self.text = "newText"
  2041.   self.width = #self.text
  2042.   self.height = 1
  2043. end
  2044.  
  2045. Objects.Text.get = function(self)
  2046.   local width = #self.text
  2047.   local buffer = Buffer:new()
  2048.   buffer:init(width, 1, self.path)
  2049.   buffer:addText(1, 1, self.text)
  2050.  
  2051.   return buffer
  2052. end
  2053.  
  2054. Objects.Text.draw = function(self)
  2055.   local x = self.x
  2056.   local y = self.y
  2057.   local text = self.text
  2058.   out.setCursorPos(x, y)
  2059.   out.write(text)
  2060. end
  2061.  
  2062. Objects.Text.getDimensions = function(self)
  2063.   local left, top, right, bottom = self.x, self.y, 1, self.y
  2064.   right = left + string.len(self.text) - 1
  2065.   return left, top, right, bottom
  2066. end
  2067.  
  2068. Objects.Text.attributeChanged = function(self, key, oldValue, newValue)
  2069.   if (key == "text") then
  2070.     self.width = #newValue
  2071.   end
  2072. end
  2073.  
  2074. -- >> Variable
  2075.  
  2076. Objects.Variable = {}
  2077. Objects.Variable.attributes = {
  2078.   { description = "Object ID", attrName = "objID", attrType = "text" };
  2079.   { description = "Load Function", attrName = "load", attrType = "text" };
  2080. }
  2081.  
  2082. Objects.Variable.new = function(self)
  2083.   self.width = 1
  2084.   self.height = 1
  2085. end
  2086.  
  2087. Objects.Variable.get = function(self, value)
  2088.   local buffer = Buffer:new()
  2089.  
  2090.   if editMode and showCustomWindow ~= "Editor" then
  2091.     buffer:init(1, 1, self.path, ObjectColors.Editor.marker)
  2092.   elseif value then
  2093.     buffer:init(string.len(value), 1, self.path)
  2094.     buffer:addText(1, 1, value)
  2095.   else
  2096.     buffer:init(1, 1, self.path)
  2097.   end
  2098.  
  2099.   return buffer
  2100. end
  2101.  
  2102. -- >> ProgressBar
  2103.  
  2104. Objects.ProgressBar = {}
  2105. Objects.ProgressBar.attributes = {
  2106.   { description = "Object ID", attrName = "objID", attrType = "text" };
  2107.   { description = "Load Function", attrName = "load", attrType = "text" };
  2108. }
  2109.  
  2110. Objects.ProgressBar.new = function(self, maxWidth)
  2111.   self.length = (maxWidth < Size.ProgressBar.length) and maxWidth or Size.ProgressBar.length
  2112.   self.lengthPercent = maxX / self.length
  2113.   self.width = self.length
  2114.   self.height = 1
  2115.   self.direction = "right"
  2116.   self.objID = "newProgressBar"
  2117.   self.canScale = true
  2118. end
  2119.  
  2120. Objects.ProgressBar.get = function(self, value)
  2121.   local length = self.length
  2122.   local direction = (isValidDirection(self.direction)) and self.direction or "right"
  2123.   local orientation = getOrientation(direction) or error("Direction " .. direction .. " is invalid!")
  2124.   value = value or ProgressBarValues[self.objID]
  2125.  
  2126.   if (orientation == "horizontal") then
  2127.     width, height = length, 1
  2128.   else
  2129.     width, height = 1, length
  2130.   end
  2131.  
  2132.   local buffer = Buffer:new()
  2133.   buffer:init(width, height, self.path, ObjectColors.ProgressBar.background)
  2134.  
  2135.   if value then
  2136.     local color
  2137.     if (value < 33) then
  2138.       color = ObjectColors.ProgressBar.low
  2139.     elseif (value > 66) then
  2140.       color = ObjectColors.ProgressBar.high
  2141.     else
  2142.       color = ObjectColors.ProgressBar.medium
  2143.     end
  2144.    
  2145.     local filled = math.floor((length / 100) * value)
  2146.     local valueBuffer = Objects.Line.get(getOrientation(direction), filled, color)
  2147.     local addX, addY = 1, 1
  2148.    
  2149.     if (direction == "left") then
  2150.       addX = width - filled + 1
  2151.     elseif (direction == "up") then
  2152.       addY = height - filled + 1
  2153.     end
  2154.    
  2155.     buffer:addBuffer(addX, addY, valueBuffer)
  2156.   end
  2157.  
  2158.   return buffer
  2159. end
  2160.  
  2161. Objects.ProgressBar.getMovePos = function(self)
  2162.   local dir = self.direction
  2163.   local length = self.length
  2164.   local x, y = Objects.getAbsolutePos(self)
  2165.  
  2166.   if (dir == "left") then
  2167.     return x + length - 1, y
  2168.   elseif (dir == "up") then
  2169.     return x, y + length - 1
  2170.   else
  2171.     return x, y
  2172.   end
  2173. end
  2174.  
  2175. Objects.ProgressBar.getScalePos = function(self)
  2176.   local dir = self.direction
  2177.   local length = self.length
  2178.   local x, y = Objects.getAbsolutePos(self)
  2179.  
  2180.   if (dir == "right") then
  2181.     return x + length - 1, y
  2182.   elseif (dir == "down") then
  2183.     return x, y + length - 1
  2184.   else
  2185.     return x, y
  2186.   end
  2187. end
  2188.  
  2189. Objects.ProgressBar.scale = function(self, x, y)
  2190.   local moveX, moveY = Objects.getMovePos(self)
  2191.   local relMoveX, relMoveY = Path.getRelativePos(self.path, moveX, moveY)
  2192.   local length
  2193.   local direction
  2194.   local newX, newY
  2195.   local width, height
  2196.  
  2197.   if (x < relMoveX and y == relMoveY) then -- Clicked left of the progressBar.
  2198.     length = relMoveX - x + 1
  2199.     direction = "left"
  2200.     newX, newY = x, y
  2201.     width, height = length, 1
  2202.   elseif (x == relMoveX and y < relMoveY) then -- Clicked above the progressBar.
  2203.     length = relMoveY - y + 1
  2204.     direction = "up"
  2205.     newX, newY = x, y
  2206.     width, height = 1, length
  2207.   elseif (x > relMoveX and y == relMoveY) then -- Clicked right of the progressBar.
  2208.     length = x - relMoveX + 1
  2209.     direction = "right"
  2210.     newX, newY = relMoveX, relMoveY
  2211.     width, height = length, 1
  2212.   elseif (x == relMoveX and y > relMoveY) then -- Clicked below the progressBar.
  2213.     length = y - relMoveY + 1
  2214.     direction = "down"
  2215.     newX, newY = relMoveX, relMoveY
  2216.     width, height = 1, length
  2217.   else
  2218.     return
  2219.   end
  2220.  
  2221.   if (length > 2) then
  2222.     local xDiff, yDiff = newX - self.x, newY - self.y
  2223.    
  2224.     self.absoluteX, self.absoluteY = self.absoluteX + xDiff, self.absoluteY + yDiff
  2225.     self.x, self.y = newX, newY
  2226.     self.direction = direction
  2227.     self.width, self.height = width, height
  2228.     self.length = length
  2229.     self.lengthPercent = length / maxX
  2230.   end
  2231. end
  2232.  
  2233. -- >> Input
  2234.  
  2235. Objects.Input = {}
  2236. Objects.Input.attributes = {
  2237. { description = "Object ID", attrName = "objID", attrType = "text" };
  2238.   { description = "Message", attrName = "message", attrType = "text" };
  2239.   { description = "Is password", attrName = "isPassword", attrType = "bool" };
  2240. }
  2241.  
  2242. Objects.Input.new = function(self)
  2243.   self.message = "Enter something."
  2244.   self.isPassword = false
  2245.   self.width = 2
  2246.   self.height = 1
  2247.   self.canClick = true
  2248. end
  2249.  
  2250. Objects.Input.get = function(self)
  2251.   local userInput = ObjectData.Input[self.objID] or ""
  2252.   local width, height = 2 + string.len(userInput), 1
  2253.  
  2254.   local buffer = Buffer:new()
  2255.   buffer:init(width, height, self.path, ObjectColors.Input.default)
  2256.   if userInput ~= "" then
  2257.     buffer:addText(2, 1, userInput)
  2258.   end
  2259.  
  2260.   return buffer
  2261. end
  2262.  
  2263. -- Returns: "text_changed" event, objID, text
  2264. Objects.Input.click = function(self)
  2265.   local x = self.absoluteX
  2266.   local y = self.absoluteY
  2267.   local objID = self.objID
  2268.   local message = self.message
  2269.   local isPassword = (self.isPassword == nil) and false or self.isPassword
  2270.   local maxLength = self.maxLength
  2271.   local existingInput = ObjectData.Input[objID]
  2272.  
  2273.   out.setBackgroundColor(ObjectColors.background)
  2274.   out.setCursorPos(x, y)
  2275.   if (existingInput ~= nil) then -- Clear the text on the input object.
  2276.     out.write(string.rep(" ", string.len(existingInput) + 2))
  2277.   else
  2278.     out.write("  ")
  2279.   end
  2280.   ObjectData.Input[objID] = nil
  2281.  
  2282.   out.setCursorPos(x, y)
  2283.   if not outIsTerm then
  2284.     -- make the input-object yellow
  2285.     out.setBackgroundColor(ObjectColors["Input"].active)
  2286.     out.write("  ")
  2287.     out.setBackgroundColor(ObjectColors.background)
  2288.   end
  2289.  
  2290.   if outIsTerm then
  2291.     out.setCursorPos(x + 1, y)
  2292.   end
  2293.  
  2294.   local userInput = readUserInput(message, isPassword)
  2295.   if (userInput ~= nil) then
  2296.     ObjectData.Input[objID] = userInput
  2297.   end
  2298.  
  2299.   out.setCursorPos(x, y)
  2300.   out.setBackgroundColor(ObjectColors.Input.default)
  2301.   out.setTextColor(ObjectColors.Input.text)
  2302.  
  2303.   out.write(" ")
  2304.   if (userInput ~= nil and userInput ~= "") then
  2305.     if isPassword then
  2306.       for i = 1, string.len(userInput) do
  2307.         out.write("*")
  2308.       end
  2309.     else
  2310.       out.write(userInput)
  2311.     end
  2312.   end
  2313.  
  2314.   out.write(" ")
  2315.   out.setBackgroundColor(ObjectColors.background)
  2316.   out.setTextColor(ObjectColors.text)
  2317.  
  2318.   return "text_changed", {self.objID, userInput}
  2319. end
  2320.  
  2321. -- >> List
  2322.  
  2323. Objects.List = {}
  2324. Objects.List.attributes = {
  2325.   { description = "Object ID", attrName = "objID", attrType = "text" };
  2326.   { description = "Items", attrName = "Items", attrType = "table" };
  2327.   { description = "Is multiselect", attrName = "isMultiselect", attrType = "bool" };
  2328. }
  2329.  
  2330. Objects.List.new = function(self)
  2331.   self.Items = {}
  2332.   self.objID = "testList"
  2333.   self.isMultiselect = false
  2334.   self.canClick = true
  2335. end
  2336.  
  2337. Objects.List.get = function(self)
  2338.   assert(self)
  2339.  
  2340.   local buffer = Buffer:new()
  2341.  
  2342.   if self.load then
  2343.     self.Items = self.load()
  2344.   end
  2345.  
  2346.   self.Items = self.Items or { "empty" }
  2347.  
  2348.   if (#self.Items == 0) then
  2349.     self.Items = { "empty" }
  2350.   end
  2351.  
  2352.   self.width = getLongestString(self.Items) + 2
  2353.   self.height = #self.Items
  2354.  
  2355.   if not ObjectData.List[self.objID] then
  2356.     ObjectData.List[self.objID] = {}
  2357.   end
  2358.  
  2359.   buffer:init(self.width, self.height, self.path, ObjectColors.List.default)
  2360.  
  2361.   local line = 1
  2362.   for key, element in pairs(self.Items) do
  2363.     if (ObjectData.List[self.objID][key]) then
  2364.       buffer:addBuffer(1, line, Objects.Line.get("horizontal", self.width, ObjectColors.List.active))
  2365.     end
  2366.     buffer:addText(2, line, self.Items[line])
  2367.     line = line + 1
  2368.   end
  2369.  
  2370.   return buffer
  2371. end
  2372.  
  2373. -- Returns: "selection_changed" event, objID, key, true or false
  2374. Objects.List.click = function(self, x, y)
  2375.   local objID = self.objID
  2376.   local isMultiselect = self.isMultiselect
  2377.   local itemSelected = ObjectData.List[objID][y]
  2378.  
  2379.   if (isMultiselect) then
  2380.     ObjectData.List[objID][y] = not itemSelected
  2381.   else
  2382.     ObjectData.List[objID] = {}
  2383.     ObjectData.List[objID][y] = true
  2384.   end
  2385.  
  2386.   Objects.draw(self)
  2387.  
  2388.   return "selection_changed", {self.objID, y, ObjectData.List[objID][y]}
  2389. end
  2390.  
  2391. -- Same as List.click
  2392. Objects.List.drag = function(self, x, y)
  2393.   return Objects.List.click(self, x, y)
  2394. end
  2395.  
  2396. Objects.List.getFirstSelectedKey = function(self)
  2397.   assert(self)
  2398.  
  2399.   local objID = self.objID
  2400.  
  2401.   for key, value in pairs(ObjectData.List[objID]) do
  2402.     if (value == true and self.Items[key] ~= "empty") then
  2403.       return key
  2404.     end
  2405.   end
  2406.  
  2407.   return nil
  2408. end
  2409.  
  2410. Objects.List.getFirstSelectedValue = function(self)
  2411.   assert(self)
  2412.  
  2413.   key = Objects.List.getFirstSelectedKey(self)
  2414.  
  2415.   if key then
  2416.     return self.Items[key]
  2417.   else
  2418.     return nil
  2419.   end
  2420. end
  2421.  
  2422. -- >> FileSelector
  2423.  
  2424. Objects.FileSelector = {}
  2425. Objects.FileSelector.attributes = {
  2426.   { description = "Object ID", attrName = "objID", attrType = "text" };
  2427. }
  2428.  
  2429. Objects.FileSelector.new = function(self)
  2430.   self.width = string.len(Text.fileSelector)
  2431.   self.height = 1
  2432.   self.isMultiselect = false
  2433.   self.canClick = true
  2434. end
  2435.  
  2436. Objects.FileSelector.get = function(self)
  2437. error("not yet implemented")
  2438.   local objectID = objectID
  2439.   local x = self.x
  2440.   local y = self.y
  2441.   local isMultiselect = self.isMultiselect
  2442.  
  2443.   out.setBackgroundColor(ObjectColors["FileSelector"].default)
  2444.   out.setTextColor(ObjectColors["FileSelector"].text)
  2445.  
  2446.   out.setCursorPos(x, y)
  2447.   out.write(Text.fileSelector)
  2448.  
  2449.   if (ObjectData.FileSelector[objectID] ~= nil) then
  2450.     out.setBackgroundColor(ObjectColors.background)
  2451.     out.setTextColor(ObjectColors.text)
  2452.     local files = ObjectData.FileSelector[objectID]
  2453.     out.write(" ")
  2454.     if (type(files) == "table") then
  2455.       local sep = ""
  2456.       for _, fileName in pairs(files) do
  2457.         term.write(sep .. fileName)
  2458.         sep = ", "
  2459.       end
  2460.     else
  2461.       out.write(files)
  2462.     end
  2463.   end
  2464.  
  2465.   out.setBackgroundColor(ObjectColors.background)
  2466.   out.setTextColor(ObjectColors.text)
  2467. end
  2468.  
  2469. Objects.FileSelector.click = function(self, x, y)
  2470. error("Not yet implemented")
  2471. -- TODO
  2472.   local finished = false
  2473.   local path = "/"
  2474.   local list = {}
  2475.  
  2476.   while not finished do
  2477.     clearScreen()
  2478.     out.setCursorPos(2, 1)
  2479.     out.write("Path: " .. path)
  2480.    
  2481.     list = getFileList(path)
  2482.    
  2483.     out.setTextColor(ObjectColors.FileSelector.text)
  2484.   end
  2485. end
  2486.  
  2487. -- >> CheckBox
  2488. Objects.CheckBox = {}
  2489. Objects.CheckBox.attributes = {
  2490.   { description = "Object ID", attrName = "objID", attrType = "text" };
  2491.   { description = "Checked by default", attrName = "defaultIsChecked", attrType = "bool" };
  2492. }
  2493.  
  2494. Objects.CheckBox.new = function(self)
  2495.   self.objID = "newCheckBox"
  2496.   self.canClick = true
  2497.   self.canScale = false
  2498.   self.defaultIsChecked = false
  2499.   self.text = "new CheckBox"
  2500.   self.width = #self.text + 2
  2501.   self.height = 1
  2502. end
  2503.  
  2504. Objects.CheckBox.init = function(self)
  2505.   if (self.defaultIsChecked == true) then
  2506.     ObjectData.CheckBox[self.objID] = true
  2507.   end
  2508. end
  2509.  
  2510. Objects.CheckBox.get = function(self)
  2511.   if (ObjectData.CheckBox[self.objID] == nil) then
  2512.     ObjectData.CheckBox[self.objID] = self.defaultIsChecked
  2513.   end
  2514.  
  2515.   local buffer = Buffer:new()
  2516.   local isChecked = ObjectData.CheckBox[self.objID]
  2517.   local char = isChecked and "X" or " "
  2518.  
  2519.   buffer:init(self.width, self.height, self.path)
  2520.   buffer:setPixel(1, 1,
  2521.     { background = ObjectColors.CheckBox.default,
  2522.       text = ObjectColors.CheckBox.active,
  2523.       char = char })
  2524.   buffer:addText(3, 1, self.text)
  2525.  
  2526.   return buffer
  2527. end
  2528.  
  2529. -- returns "checked" event, object ID, checked state
  2530. Objects.CheckBox.click = function(self)
  2531.   ObjectData.CheckBox[self.objID] = not ObjectData.CheckBox[self.objID]
  2532.   Objects.draw(self)
  2533.  
  2534.   return "checked", { self.objID, ObjectData.CheckBox[self.objID] }
  2535. end
  2536.  
  2537. Objects.CheckBox.attributeChanged = function(self, key, oldValue, newValue)
  2538.   if (key == "text") then
  2539.     self.width = #newValue + 2
  2540.   end
  2541. end
  2542.  
  2543. -- >> RadioButton
  2544.  
  2545. Objects.RadioButton = {}
  2546. Objects.RadioButton.attributes = {
  2547.   { description = "Object ID", attrName = "objID", attrType = "text" };
  2548.   { description = "Text", attrName = "text", attrType = "text" };
  2549.   { description = "Group", attrName = "group", attrType = "text" };
  2550.   { description = "Checked by default", attrName = "defaultIsChecked", attrType = "bool" }
  2551. }
  2552.  
  2553. Objects.RadioButton.new = function(self)
  2554.   self.objID = "newRadioButton1"
  2555.   self.defaultIsChecked = false
  2556.   self.group = "default"
  2557.   self.text = "new RadioButton"
  2558.   self.width = #self.text + 2
  2559.   self.height = 1
  2560.   self.canClick = true
  2561.   self.canScale = false
  2562. end
  2563.  
  2564. Objects.RadioButton.init = function(self)
  2565.   if (self.defaultIsChecked == true) then
  2566.     ObjectData.RadioButton[self.objID] = true
  2567.   end
  2568. end
  2569.  
  2570. Objects.RadioButton.get = function(self)
  2571.   local buffer = Buffer:new()
  2572.   local color
  2573.  
  2574.   if ObjectData.RadioButton[self.objID] and not editMode then
  2575.     color = ObjectColors.RadioButton.active
  2576.   else
  2577.     color = ObjectColors.RadioButton.default
  2578.   end
  2579.  
  2580.   buffer:init(#self.text + 2, 1, self.path)
  2581.   buffer:setPixel(1, 1, { background = color, char = " "})
  2582.   buffer:addText(3, 1, self.text)
  2583.  
  2584.   return buffer
  2585. end
  2586.  
  2587. Objects.RadioButton.update = function(container, groupName, clickedID)
  2588.   for _, object in pairs(container.Children) do
  2589.     if (type(object) == "table") then
  2590.       if (object.objType == "RadioButton" and object.group == groupName) then
  2591.         ObjectData.RadioButton[object.objID] = (clickedID == object.objID)
  2592.         Objects.draw(object)
  2593.       elseif (object.isContainer) then
  2594.         Objects.RadioButton.update(object, groupName, clickedID)
  2595.       end
  2596.     end
  2597.   end
  2598. end
  2599.  
  2600. -- returns "radio_changed" event, object ID
  2601. Objects.RadioButton.click = function(self)
  2602.   Objects.RadioButton.update(getCurrentWindow(), self.group, self.objID)
  2603.  
  2604.   return "selection_changed", { self.objID }
  2605. end
  2606.  
  2607. Objects.RadioButton.attributeChanged = function(self, key, oldValue, newValue)
  2608.   if (key == "text") then
  2609.     self.with = #newValue + 2
  2610.   end
  2611. end
  2612.  
  2613. -- >> Slider
  2614.  
  2615. Objects.Slider = {}
  2616. Objects.Slider.attributes = {
  2617.   { description = "Object ID", attrName = "objID", attrType = "text" };
  2618.   { description = "Minimum value", attrName = "minValue", attrType = "number" };
  2619.   { description = "Maximum value (affects length)", attrName = "maxValue", attrType = "number" };
  2620. }
  2621.  
  2622. Objects.Slider.new = function(self)
  2623.   self.objID = "newSlider"
  2624.   self.width = Size.Slider.length
  2625.   self.height = 1
  2626.   self.minValue = 0
  2627.   self.maxValue = Size.Slider.length - 1
  2628.   self.canClick = true
  2629.   self.canScale = false
  2630. end
  2631.  
  2632. Objects.Slider.init = function(self)
  2633.   ObjectData.Slider[self.objID] = self.minValue
  2634. end
  2635.  
  2636. Objects.Slider.get = function(self)
  2637.   local buffer = Buffer:new()
  2638.  
  2639.   buffer:init(self.width, self.height, self.path, ObjectColors.Slider.default, ObjectColors.Slider.text)
  2640.   buffer:addText(1, 1, "|" .. string.rep("-", self.width - 2) .. "|")
  2641.  
  2642.   if ObjectData.Slider[self.objID] and not editMode then
  2643.     local pixel = { background = ObjectColors.Slider.active, text = ObjectColors.Slider.activeText, char = "|" }
  2644.     buffer:setPixel(ObjectData.Slider[self.objID] - self.minValue + 1, 1, pixel)
  2645.   end
  2646.  
  2647.   return buffer
  2648. end
  2649.  
  2650. Objects.Slider.attributeChanged = function(self, key, oldValue, newValue)
  2651.   if ((key == "minValue" or key == "manValue") and newValue % 1 ~= 0) then
  2652.     self[key] = round(newValue)
  2653.   end
  2654.  
  2655.   if (key == "minValue") then
  2656.     if (newValue >= self.maxValue) then
  2657.       self[key] = oldValue
  2658.     else -- adapt slider width
  2659.       self.width = self.maxValue - self.minValue + 1
  2660.     end
  2661.   elseif (key == "maxValue") then
  2662.     if (newValue <= self.minValue) then
  2663.       self[key] = oldValue
  2664.     else -- adapt slider width
  2665.       self.width = self.maxValue - self.minValue + 1
  2666.     end
  2667.   end
  2668. end
  2669.  
  2670. -- returns "slider_changed" event, object ID, value
  2671. Objects.Slider.click = function(self, x, y)
  2672.   ObjectData.Slider[self.objID] = x + self.minValue - 1
  2673.   Objects.draw(self)
  2674.  
  2675.   return "slider_changed", { self.objID, ObjectData.Slider[self.objID] }
  2676. end
  2677.  
  2678. Objects.Slider.drag = function(self, x, y)
  2679.   return Objects.Slider.click(self, x, y)
  2680. end
  2681.  
  2682. -- >> DropDownList
  2683.  
  2684. Objects.DropDownList = {}
  2685. Objects.DropDownList.attributes = {
  2686.   { description = "Object ID", attrName = "objID", attrType = "text" };
  2687.   { description = "Items", attrName = "Items", attrType = "table" };
  2688.   { description = "Load Function", attrName = "load", attrType = "text" };
  2689. }
  2690.  
  2691. Objects.DropDownList.new = function(self, maxWidth)
  2692.   self.Items = { "empty" }
  2693.   self.objID = "newDropDownList"
  2694.   self.width = (Size.DropDownList.width > maxWidth) and maxWidth or Size.DropDownList.width
  2695.   self.height = 1
  2696.   self.canClick = true
  2697.   self.canScale = true
  2698. end
  2699.  
  2700. Objects.DropDownList.get = function(self)
  2701.   assert(self)
  2702.  
  2703.   if self.load then
  2704.     self.Items = self.load()
  2705.   elseif not self.Items then
  2706.     self.Items = { "empty" }
  2707.   end
  2708.  
  2709.   local buffer = Buffer:new()
  2710.   buffer:init(self.width, 1, self.path, ObjectColors.DropDownList.default, ObjectColors.DropDownList.text)
  2711.  
  2712.   if ObjectData.DropDownList[self.objID] then
  2713.     buffer:addText(1, 1, self.Items[ObjectData.DropDownList[self.objID]])
  2714.   elseif #self.Items > 0 then
  2715.     ObjectData.DropDownList[self.objID] = 1
  2716.     buffer:addText(1, 1, self.Items[ObjectData.DropDownList[self.objID]])
  2717.   end
  2718.  
  2719.   buffer:addText(self.width - 2, 1, " V")
  2720.  
  2721.   return buffer
  2722. end
  2723.  
  2724. -- Returns: "selection_changed" event, objID, key
  2725. Objects.DropDownList.click = function(self, x, y)
  2726.   assert(self)
  2727.  
  2728.   local selectedItem = ObjectData.DropDownList[self.objID]
  2729.   local drawY
  2730.   local height = #self.Items
  2731.  
  2732.   -- Determine whether the list should be displayed below or above the object.
  2733.  
  2734.   if height >= maxX then -- Not enough space vertically.
  2735.     drawY = 1
  2736.   elseif maxX - height > self.absoluteY then
  2737.     drawY = self.absoluteY + 1
  2738.   elseif self.absoluteY - height > 0 then
  2739.     drawY = self.absoluteY - height
  2740.   else
  2741.     drawY = maxY - height
  2742.   end
  2743.  
  2744.   out.setTextColor(ObjectColors.DropDownList.text)
  2745.  
  2746.   for index = 1, height do
  2747.     if (index == ObjectData.DropDownList[self.objID]) then
  2748.       out.setBackgroundColor(ObjectColors.DropDownList.active)
  2749.     else
  2750.       out.setBackgroundColor(ObjectColors.DropDownList.default)
  2751.     end
  2752.    
  2753.     out.setCursorPos(self.absoluteX, drawY + index - 1)
  2754.     out.write(self.Items[index] .. string.rep(" ", self.width - #self.Items[index] - 1))
  2755.   end
  2756.  
  2757.   out.setBackgroundColor(ObjectColors.background)
  2758.   out.setTextColor(ObjectColors.text)
  2759.  
  2760.   local x, y, mouseButton = getCursorInput()
  2761.  
  2762.   if (x >= self.absoluteX and
  2763.       x <= self.absoluteX + self.width - 1 and
  2764.       y >= self.absoluteY + 1 and
  2765.       y <= self.absoluteY + height) then
  2766.     local selectedIndex = y - self.absoluteY
  2767.    
  2768.     ObjectData.DropDownList[self.objID] = selectedIndex
  2769.   end
  2770.  
  2771.   drawWindow()
  2772.  
  2773.   return "selection_changed", { self.objID, ObjectData.DropDownList[self.objID] }
  2774. end
  2775.  
  2776. Objects.DropDownList.scale = function(self, x, y)
  2777.   assert(self)
  2778.   assert(x)
  2779.   assert(y)
  2780.  
  2781.   local length = x - self.x + 1
  2782.  
  2783.   if (length >= 3) then
  2784.     self.width = length
  2785.   end
  2786. end
  2787.  
  2788. -- >>> Containers
  2789.  
  2790. Objects.Container = {}
  2791.  
  2792. Objects.Container.getNextFreeKey = function(self)
  2793.   local nextKey = 1
  2794.  
  2795.   while self.Children[nextKey] ~= nil do
  2796.     nextKey = nextKey + 1
  2797.   end
  2798.  
  2799.   return nextKey
  2800. end
  2801.  
  2802. -- Returns the area of the container which stores
  2803. -- its children.
  2804. Objects.Container.getContentArea = function(self)
  2805.   assert(self)
  2806.   local objType = self.objType
  2807.  
  2808.   if (Objects.Container[objType].getContentArea) then
  2809.     return Objects.Container[objType].getContentArea(self)
  2810.   else
  2811.     local left = self.x + 1
  2812.     local top = self.y + 1
  2813.     local right = self.x + self.width - 2
  2814.     local bottom = self.y + self.height - 2
  2815.     return left, top, right, bottom
  2816.   end
  2817. end
  2818.  
  2819. -- Determines whether the container itself or its
  2820. -- content area is at the given position.
  2821. Objects.Container.contentAreaClicked = function(self, x, y)
  2822.   local left, top, right, bottom = Objects.Container.getContentArea(self)    
  2823.  
  2824.   return (x >= left and x <= right and y >= top and y <= bottom)
  2825. end
  2826.  
  2827. -- Draws the buffer and trims it before that if
  2828. -- necessary.
  2829. Objects.Container.drawBuffer = function(self, buffer, path, x, y, absoluteX, absoluteY, nestLevel)
  2830.   --log("Objects.Container.drawBuffer", "FUNC")
  2831.   local modX, modY = Objects.Container.getPosModifier(self, x, y)
  2832.   x, y = x + modX, y + modY
  2833.  
  2834.   if (self.objType ~= "Window") then
  2835.     local containerLeft, containerTop, containerRight, containerBottom = 1, 1, self.width - 1, self.height - 1
  2836.     local objLeft, objTop, objRight, objBottom = x, y, x + buffer.width - 1, y + buffer.height - 1
  2837.    
  2838.     --log("Container dimensions: left: " .. containerLeft .. ", top: " .. containerTop .. ", right: " .. containerRight .. ", bottom: " ..containerBottom .. ".", "DEBUG")
  2839.     --log("Object dimensions: left: " .. objLeft .. ", top: " .. objTop .. ", right: " .. objRight .. ", bottom: " ..objBottom .. ".", "DEBUG")
  2840.    
  2841.     if (objLeft > self.width or
  2842.         objTop > self.height or
  2843.         objRight < 0 or
  2844.         objBottom < 0) then
  2845.       return
  2846.     elseif (objLeft < containerLeft or objTop < containerTop or objRight >= containerRight or objBottom >= containerBottom) then
  2847.       -- Object goes over the border. Trim it.
  2848.       local trimLeft = (containerLeft - objLeft) > 0 and containerLeft - objLeft or 0
  2849.       local trimTop = (containerTop - objTop) > 0 and containerTop - objTop or 0
  2850.       local trimRight = (objRight - containerRight + 1) > 0 and objRight - containerRight + 1 or 0
  2851.       local trimBottom = (objBottom - containerBottom + 1) > 0 and objBottom - containerBottom + 1 or 0
  2852.       buffer = buffer:trim(trimLeft, trimTop, trimRight, trimBottom)
  2853.      
  2854.       if (trimLeft > 0) then
  2855.         x = x + trimLeft
  2856.         absoluteX = absoluteX + trimLeft
  2857.       end
  2858.       if (trimTop > 0) then
  2859.         y = y + trimTop
  2860.         absoluteY = absoluteY + trimTop
  2861.       end
  2862.     end
  2863.   end
  2864.  
  2865.   if (nestLevel < #path - 1) then
  2866.     local container = Path.getContainerAt(path, nestLevel + 1)
  2867.     local relX, relY = Objects.Container.getRelativePos(container, x, y)
  2868.     Objects.Container.drawBuffer(container, buffer, path, relX, relY, absoluteX, absoluteY, nestLevel + 1)
  2869.   else
  2870.     buffer:draw(absoluteX + modX, absoluteY + modY)
  2871.   end
  2872. end
  2873.  
  2874. Objects.Container.getRelativePos = function(self, x, y)
  2875.   local left, top, right, bottom = Objects.Container.getContentArea(self)
  2876.  
  2877.   --log("Objects.Container.getRelativePos", "FUNC")
  2878.   --log(self.objType .. " ID: " .. self.objID .. ", x: " .. x .. ", y: " .. y .. ".", "ATTR")
  2879.   --log("Left: " .. left .. ", top: " .. top .. ", right: " .. right .. ", bottom: " .. bottom .. ".", "DEBUG")
  2880.  
  2881.   retX = x - left + 1
  2882.   retY = y - top + 1
  2883.  
  2884.   return retX, retY
  2885. end
  2886.  
  2887. Objects.Container.getParentsRelativePos = function(self, x, y)
  2888.   local left, top, right, bottom = Objects.Container.getContentArea(self)
  2889.  
  2890.   return x + left - 1, y + top - 1
  2891. end
  2892.  
  2893. Objects.Container.new = function(self, maxWidth, maxHeight)
  2894.   --log("Objects.Container.new", "FUNC")
  2895.   local defaultWidth, defaultHeight = Size.Container.width, Size.Container.height
  2896.  
  2897.   self.isContainer = true
  2898.   self.canScale = true
  2899.   self.Children = {}
  2900.   self.width = (maxWidth < defaultWidth) and maxWidth or defaultWidth
  2901.   self.height = (maxHeight < defaultHeight) and maxHeight or defaultHeight
  2902.  
  2903.   --log("New container dimensioins: " .. self.width .. " x " .. self.height .. ".")
  2904.  
  2905.   if (Objects.Container[self.objType].new) then
  2906.     Objects.Container[self.objType].new(self)
  2907.   end
  2908. end
  2909.  
  2910. Objects.Container.init = function(self)
  2911.   assert(self)
  2912.  
  2913.   local objType = self.objType
  2914.  
  2915.   if (Objects.Container[objType] and Objects.Container[objType].init) then
  2916.     Objects.Container[objType].init()
  2917.   end
  2918.  
  2919.   for _, object in pairs(self.Children) do
  2920.     if (type(object) == "table") then
  2921.       local objType = object.objType
  2922.      
  2923.       if (Objects[objType] and Objects[objType].init) then
  2924.         log("Initializting " .. objType .. " " .. object.objID .. ".", "INFO")
  2925.         Objects[objType].init(object)
  2926.       elseif (object.isContainer) then
  2927.         Objects.Container.init(object)
  2928.       end
  2929.     end
  2930.   end
  2931. end
  2932.  
  2933. Objects.Container.get = function(self)
  2934.   assert(self)
  2935.  
  2936.   local left, top, right, bottom = Objects.Container.getContentArea(self)
  2937.   local minWidth, minHeight = right - left + 1, bottom - top + 1
  2938.   local width, height = getNecessaryBufferSize(self.Children, minWidth, minHeight)
  2939.  
  2940.   local buffer = Buffer:new()
  2941.   buffer:init(width, height, {})
  2942.  
  2943.   for objectID, object in pairs(self.Children) do
  2944.     local objectBuffer = Objects.get(object)
  2945.     buffer:addBuffer(object.x, object.y, objectBuffer)
  2946.   end
  2947.  
  2948.   return Objects.Container[self.objType].get(self, buffer)
  2949. end
  2950.  
  2951. Objects.Container.getPosModifier = function(self, x, y)
  2952.   assert(self)
  2953.  
  2954.   --log("Objects.Container.getPosModifier", "FUNC")
  2955.   --log("Object type: " .. tostring(self.objType) .. ", ID: " .. tostring(self.objID) .. ".", "INFO")
  2956.   --log("Position: " .. x .. ", " .. y .. ".", "INFO")
  2957.  
  2958.   local objType = self.objType
  2959.   if (Objects.Container[objType] and Objects.Container[objType].getPosModifier) then
  2960.     local newX, newY = Objects.Container[objType].getPosModifier(self, x, y)
  2961.     --log("Modifier: " .. newX .. ", " .. newY .. ".", "INFO")
  2962.     return newX, newY
  2963.   else
  2964.     --log("Position not modified.")
  2965.     return 0, 0
  2966.   end
  2967. end
  2968.  
  2969. Objects.Container.move = function(self, addX, addY)
  2970.   self.absoluteX = self.absoluteX + addX
  2971.   self.absoluteY = self.absoluteY + addY
  2972.  
  2973.   for _, child in pairs(self.Children) do
  2974.     if child.isContainer then
  2975.       Objects.Container.move(child, addX, addY)
  2976.     else
  2977.       child.absoluteX = child.absoluteX + addX
  2978.       child.absoluteY = child.absoluteY + addY
  2979.     end
  2980.   end
  2981. end
  2982.  
  2983. -- >> Window
  2984.  
  2985. Objects.Container.Window = {}
  2986. Objects.Container.Window.new = function(windowName)
  2987.   local object = {}
  2988.   object.objType = "Window"
  2989.   object.Children = {}
  2990.   object.width, object.height = maxX, maxY
  2991.  
  2992.   return object
  2993. end
  2994.  
  2995. Objects.Container.Window.create = function(windowName)
  2996.   assert(windowName)
  2997.  
  2998.   local windowContainer = getWindowContainer()
  2999.   local object = Objects.Container.Window.new(windowName)
  3000.  
  3001.   windowContainer.Children[windowName] = object
  3002.  
  3003.   log("Created window \"" .. windowName .. "\" for window container \"" .. tostring(showCustomWindow) .. "\".", "INFO")
  3004. end
  3005.  
  3006. Objects.Container.Window.get = function(self, contentBuffer)
  3007.   return contentBuffer
  3008. end
  3009.  
  3010. Objects.Container.Window.getContentArea = function(self)
  3011.   return 1, 1, maxX, maxY
  3012. end
  3013.  
  3014. -- >> Panel
  3015.  
  3016. Objects.Container.Panel = {}
  3017. Objects.Container.Panel.attributes = {}
  3018.  
  3019. Objects.Container.Panel.get = function(self, contentBuffer)
  3020.   --log("Objects.Container.Panel.get", "FUNC")
  3021.   local buffer = Buffer:new()
  3022.   buffer:init(self.width, self.height, self.path, ObjectColors.Container.Panel.border)
  3023.   buffer:addBuffer(2, 2, contentBuffer)
  3024.   buffer:addBuffer(self.width, 1, Objects.Line.get("vertical", self.height, ObjectColors.Container.Panel.border, self.path))
  3025.   buffer:addBuffer(1, self.height, Objects.Line.get("horizontal", self.width, ObjectColors.Container.Panel.border, self.path))
  3026.  
  3027.   return buffer
  3028. end
  3029.  
  3030. -- >> ScrollView
  3031.  
  3032. Objects.Container.ScrollView = {}
  3033. Objects.Container.ScrollView.attributes = {}
  3034.  
  3035. Objects.Container.ScrollView.new = function(self)
  3036.   self.scrollX = 0
  3037.   self.scrollY = 0
  3038.   self.maxScrollX = 0
  3039.   self.maxScrollY = 0
  3040.   self.scrollXEnabled = false
  3041.   self.scrollYEnabled = true
  3042. end
  3043.  
  3044. Objects.Container.ScrollView.get = function(self, contentBuffer)
  3045.   --log("Objects.Container.ScrollView.get", "FUNC")
  3046.   --log("ScrollView (ID: " .. tostring(self.objID) .. ")", "INFO")
  3047.  
  3048.   local buffer = Buffer:new()
  3049.   buffer:init(self.width, self.height, self.path, ObjectColors.Container.ScrollView.border)
  3050.   buffer:addBuffer(2 - self.scrollX, 2 - self.scrollY, contentBuffer)
  3051.   buffer:makeBorder(self.path, ObjectColors.Container.ScrollView.border)
  3052.  
  3053.   --local edgePixel = { background=ObjectColors.Container.ScrollView.border, path=self.path }
  3054.   --buffer:setPixel(self.width, 1, edgePixel)
  3055.   --buffer:setPixel(self.width, self.height, edgePixel)
  3056.   --buffer:setPixel(1, self.height, edgePixel)
  3057.  
  3058.   if (self.scrollXEnabled) then
  3059.     self.maxScrollX = contentBuffer.width - self.width + 1
  3060.    
  3061.     buffer:addBuffer(2, self.height, Objects.Line.get("horizontal", self.width - 2, ObjectColors.Container.ScrollView.scrollBackground))
  3062.    
  3063.     if (self.width > 4) then
  3064.       local scrollBarInfo = getScrollBarInfo(self.scrollX, self.width, contentBuffer.width)
  3065.       buffer:addBuffer(scrollBarInfo.pos + 3, self.height, Objects.Line.get("horizontal", scrollBarInfo.size, ObjectColors.Container.ScrollView.scrollForeground, self.path))
  3066.     end
  3067.    
  3068.     buffer:setPixel(2, self.height, { char="<", path=self.path })
  3069.     buffer:setPixel(self.width - 1, self.height, { char=">", path=self.path })
  3070.   else
  3071.     --buffer:addBuffer(2, self.height, Objects.Line.get("horizontal", self.width - 2, ObjectColors.Container.ScrollView.border, self.path))
  3072.   end
  3073.  
  3074.   if (self.scrollYEnabled) then
  3075.     self.maxScrollY = contentBuffer.height - self.height + 1
  3076.     buffer:addBuffer(self.width, 2, Objects.Line.get("vertical", self.height - 2, ObjectColors.Container.ScrollView.scrollBackground))
  3077.    
  3078.     if (self.height > 4) then
  3079.       local scrollBarInfo = getScrollBarInfo(self.scrollY, self.height, contentBuffer.height)
  3080.       buffer:addBuffer(self.width, scrollBarInfo.pos + 3, Objects.Line.get("vertical", scrollBarInfo.size, ObjectColors.Container.ScrollView.scrollForeground, self.path))
  3081.     end
  3082.    
  3083.     buffer:setPixel(self.width, 2, { char="^", path=self.path })
  3084.     buffer:setPixel(self.width, self.height - 1, { char="V", path=self.path })
  3085.   else
  3086.     --buffer:addBuffer(self.width, 2, Objects.Line.get("vertical", self.height - 2, ObjectColors.Container.ScrollView.border, self.path))
  3087.   end
  3088.  
  3089.   return buffer
  3090. end
  3091.  
  3092. Objects.Container.ScrollView.click = function(self, x, y)
  3093.   if (x == self.width and y == 2) then -- Up
  3094.     --log("Up")
  3095.     if (self.scrollY > 0) then
  3096.       self.scrollY = self.scrollY - 1
  3097.     end
  3098.   elseif (x == self.width and y == self.height - 1) then -- Down
  3099.     --log("Down")
  3100.     if (self.scrollY < self.maxScrollY) then
  3101.       self.scrollY = self.scrollY + 1
  3102.     end
  3103.   elseif (x == 2 and y == self.height) then -- Left
  3104.     --log("Left")
  3105.     if (self.scrollX > 0) then
  3106.       self.scrollX = self.scrollX - 1
  3107.     end
  3108.   elseif (x == self.width - 1 and y == self.height) then -- Right
  3109.     --log("Right")
  3110.     if (self.scrollX < self.maxScrollX) then
  3111.       self.scrollX = self.scrollX + 1
  3112.     end
  3113.   end
  3114.  
  3115.   WindowBuffer:addBuffer(self.absoluteX, self.absoluteY, Objects.get(self))
  3116.   Objects.draw(self)
  3117. end
  3118.  
  3119. Objects.Container.ScrollView.getPosModifier = function(self, x, y)
  3120.   return self.scrollX * -1, self.scrollY * -1
  3121. end
  3122.  
  3123. Objects.Container.ScrollView.addMarker = function(self, buffer)
  3124.  
  3125.   if not (self.scrollXEnabled and self.scrollYEnabled) then
  3126.     if (not self.scrollXEnabled and self.width > 2) then
  3127.       buffer:addBuffer(2, self.height, Objects.Line.get("horizontal", self.width - 2, ObjectColors.Editor.editMarker))
  3128.     end
  3129.    
  3130.     if (not self.scrollYEnabled and self.height > 2) then
  3131.       buffer:addBuffer(self.width, 2, Objects.Line.get("vertical", self.height - 2, ObjectColors.Editor.editMarker))
  3132.     end
  3133.   end
  3134. end
  3135.  
  3136. Objects.Container.ScrollView.editorClick = function(self, x, y)
  3137.   -- Check whether an arrow has been clicked.
  3138.   if (self.scrollYEnabled and x == self.width and y == 2) then -- Up
  3139.     --log("Up")
  3140.     if (self.scrollY > 0) then
  3141.       self.scrollY = self.scrollY - 1
  3142.     end
  3143.    
  3144.     return true
  3145.   elseif (self.scrollYEnabled and x == self.width and y == self.height - 1) then -- Down
  3146.     --log("Down")
  3147.     self.scrollY = self.scrollY + 1
  3148.     return true
  3149.   elseif (self.scrollXEnabled and x == 2 and y == self.height) then -- Left
  3150.     --log("Left")
  3151.     if (self.scrollX > 0) then
  3152.       self.scrollX = self.scrollX - 1
  3153.     end
  3154.    
  3155.     return true
  3156.   elseif (self.scrollXEnabled and x == self.width - 1 and y == self.height) then -- Right
  3157.     --log("Right")
  3158.     self.scrollX = self.scrollX + 1
  3159.     return true
  3160.   else -- Check whether the scrollBar has been clicked.
  3161.     if (x >= 1 and x <= self.width - 1 and y == self.height) then
  3162.       self.scrollXEnabled = not self.scrollXEnabled
  3163.       return true
  3164.     elseif (x == self.width and y > 1 and y < self.height - 1) then
  3165.       self.scrollYEnabled = not self.scrollYEnabled
  3166.       return true
  3167.     end
  3168.   end
  3169.  
  3170.   return false
  3171. end
  3172.  
  3173. -- >> Non-addable objects.
  3174.  
  3175. -- >> Line
  3176.  
  3177. Objects.Line = {}
  3178. Objects.Line.get = function(orientation, length, color, path)
  3179.   if (orientation ~= "horizontal" and orientation ~= "vertical") then
  3180.     orientation = getOrientation(orientation) or error("Orientation " .. orientation .. " is invalid!", 1)
  3181.   end
  3182.  
  3183.   assert(length)
  3184.   assert(color)
  3185.  
  3186.   local width, height
  3187.   if (orientation == "horizontal") then
  3188.     width, height = length, 1
  3189.   else
  3190.     width, height = 1, length
  3191.   end
  3192.  
  3193.   local buffer = Buffer:new()
  3194.   buffer:init(width, height, path, color)
  3195.  
  3196.   return buffer
  3197. end
  3198.  
  3199. -- >> Selector
  3200. Objects.Selector = {}
  3201. Objects.Selector.draw = function(x, y, items)
  3202.   assert(x)
  3203.   assert(y)
  3204.   assert(items)
  3205.  
  3206.   width = getLongestString(items) + 2
  3207.   height = #items -- Items + up and down
  3208.   itemCount = #items
  3209.   displayCount = itemCount
  3210.  
  3211.   enoughXSpace = true
  3212.   -- determine where the selector should actually be displayed
  3213.   if (width > maxX) then -- Not enough monitors horizontally?
  3214.     x = 1
  3215.     enoughXSpace = false
  3216.   elseif (maxX - x < width) then -- Not enough space to the right.
  3217.     if (x >= width) then -- Let's see if there is space to the left.
  3218.       x = x - width
  3219.     else -- No space? Check where you've got more space.
  3220.       if (maxX / 2) > x then -- More space to the left.
  3221.         x = maxX - width + 1
  3222.         enoughXSpace = false
  3223.       else -- More space to the right
  3224.         x = 1
  3225.         enoughXSpace = false
  3226.       end
  3227.     end
  3228.   else -- Enough space to the right.
  3229.     x = x + 1
  3230.   end
  3231.  
  3232.   if (height > maxY - y) then -- Not enough space from y to bottom.
  3233.     if ((maxY / 2) > y) then -- More space below y.
  3234.       if enoughXSpace then
  3235.         if (maxY < height) then -- Too big for the whole screen.
  3236.           y = 1
  3237.           displayCount = maxY
  3238.         else -- Enough space next to x and not too high.
  3239.           y = maxY - height
  3240.         end
  3241.       else -- Can't display it next to the selected point.
  3242.         y = y + 1
  3243.         displayCount = maxY - y + 1
  3244.       end
  3245.     else -- More space above y.
  3246.       if enoughXSpace then
  3247.         if (y < height) then -- Not enough space from top to y.
  3248.           if (maxY < height) then -- Too big for the whole screen.
  3249.             y = 1
  3250.             displayCount = maxY
  3251.           else -- Enough space next to x and not too high.
  3252.             y = 1
  3253.           end
  3254.         else -- Enough space from top to y.
  3255.           y = y - height + 1
  3256.         end
  3257.       else
  3258.         if (y < height) then -- Not enough space from top to y.
  3259.           if (maxY < height) then -- Too big for the whole screen.
  3260.             y = 1
  3261.             displayCount = maxY
  3262.           else -- Not enough space next to x but not too high.
  3263.             y = 1
  3264.             displayCount = y - 2
  3265.           end
  3266.         else -- Enough space from top to y.
  3267.           y = y - height
  3268.         end
  3269.       end
  3270.     end
  3271.   end
  3272.  
  3273.   out.setBackgroundColor(ObjectColors.background)
  3274.  
  3275.   local drawArrows = displayCount < height
  3276.  
  3277.   local start = 1
  3278.   local scroll = 0
  3279.   local right = x + width - 1
  3280.   local bottom = y + height - 1
  3281.   local finished = false
  3282.   local result
  3283.  
  3284.   if drawArrows then
  3285.     displayCount = displayCount - 2
  3286.     local middle = math.floor(width / 2)
  3287.    
  3288.     out.setCursorPos(x + middle, y)
  3289.     out.write("^")
  3290.     out.setCursorPos(x + middle, bottom)
  3291.     out.write("V")
  3292.    
  3293.     start = 2
  3294.   end
  3295.  
  3296.   while not finished do
  3297.     out.setTextColor(ObjectColors.Editor.selectorText)
  3298.    
  3299.     for row = start, displayCount do
  3300.       local color = (row % 2 == 0) and ObjectColors.Editor.selector1 or ObjectColors.Editor.selector2
  3301.       local text = items[row + scroll]
  3302.      
  3303.       out.setBackgroundColor(color)
  3304.       out.setCursorPos(x, y + row - 1)
  3305.       out.write(" " .. text .. string.rep(" ", width - #text - 1))
  3306.     end
  3307.    
  3308.     out.setBackgroundColor(ObjectColors.background)
  3309.    
  3310.     local touchX, touchY, mouseButton = getCursorInput()
  3311.    
  3312.     if (touchX < x or touchX > right or touchY < y or touchY > bottom) then
  3313.       selectedItem = nil
  3314.       result = false
  3315.       finished = true
  3316.     else -- User touched the selector.
  3317.       if showArrows then
  3318.         if (touchY == y) then -- up
  3319.           if (scroll > 0) then -- Check whether it is possible to scroll up.
  3320.             scroll = scroll - 1
  3321.           end
  3322.         elseif (touchY == bottom) then -- down
  3323.           if (displayCount < itemCount) then
  3324.             if (scroll < itemCount - displayCount) then
  3325.               scroll = scroll + 1
  3326.             end
  3327.           end
  3328.         end
  3329.       else
  3330.         selectedItem = items[touchY - y + scroll + 1]
  3331.         result = true
  3332.         finished = true
  3333.       end
  3334.     end
  3335.   end
  3336.  
  3337.   drawWindow()
  3338.   return result
  3339. end
  3340.  
  3341. -- >> API Functions
  3342.  
  3343. -- API function: Sets the value of all variables
  3344. -- with the given ID.
  3345. function setVariableValue(variableID, newVar)
  3346.   VariableValues[variableID] = newVar
  3347. end
  3348.  
  3349. -- API function: Sets the value of all progressBars
  3350. -- with the given ID.
  3351. function setProgressBarValue(objID, newVar)
  3352.   ProgressBarValues[objID] = newVar
  3353. end
  3354.  
  3355. -- >> User Input Functions
  3356.  
  3357. -- Gets any input of the user
  3358. -- (not from the environment)
  3359. function getAnyInput()
  3360.   local finished = false
  3361.   local event = {}
  3362.  
  3363.   while not finished do
  3364.     finished = true
  3365.     os.sleep(0)
  3366.    
  3367.     input = {os.pullEvent()}
  3368.     event.eventType = input[1]
  3369.     event.dragged = false
  3370.    
  3371.     if (event.eventType == "monitor_touch" and not outIsTerm) then
  3372.       event.eventType = "mouse"
  3373.       event.x = input[3]
  3374.       event.y = input[4]
  3375.       event.mouseButton = 1
  3376.     elseif (event.eventType == "mouse_click" and outIsTerm) then
  3377.       event.eventType = "mouse"
  3378.       event.x = input[3]
  3379.       event.y = input[4]
  3380.       event.mouseButton = input[2]
  3381.     elseif (event.eventType == "mouse_drag") then
  3382.       event.eventType = "mouse"
  3383.       event.x = input[3]
  3384.       event.y = input[4]
  3385.       event.mouseButton = input[2]
  3386.       event.dragged = true
  3387.     elseif (event.eventType == "key") then
  3388.       event.key = input[2]
  3389.     else
  3390.       finished = false
  3391.     end
  3392.   end
  3393.  
  3394.   return event
  3395. end
  3396.  
  3397. -- Returns where the user clicked and which button
  3398. -- he pressed (always 1 if it's a monitor).
  3399. function getCursorInput(includeDrag)
  3400.   local finished = false
  3401.   local dragged = false -- Determines whether the event is "mouse_click" or "mouse_drag"
  3402.  
  3403.   while not finished do
  3404.     event, param, x, y = os.pullEvent()
  3405.    
  3406.     if (event == "monitor_touch" and not outIsTerm) then
  3407.       mouseButton = 1
  3408.       finished = true
  3409.     elseif (event == "mouse_click" and outIsTerm) then
  3410.       mouseButton = param
  3411.       finished = true
  3412.     elseif (event == "mouse_drag" and includeDrag) then
  3413.       mouseButton = param
  3414.       dragged = true
  3415.       finished = true
  3416.     end
  3417.   end
  3418.  
  3419.   return x, y, mouseButton, dragged
  3420. end
  3421.  
  3422. -- Waits until any key gets pressed.
  3423. function getKeyInput()
  3424.   os.pullEvent("key")
  3425. end
  3426.  
  3427. function readUserInput(message, isPassword)
  3428.   if not outIsTerm then
  3429.     print(message)
  3430.   end
  3431.    
  3432.   if isPassword  then
  3433.     ret = read("*")
  3434.   else
  3435.     ret = read()
  3436.   end
  3437.  
  3438.   return ret
  3439. end
  3440.  
  3441. -- >> Display Functions
  3442.  
  3443. -- Has to be used instead of paintutils.drawpixel
  3444. function drawPixel(x, y, color)
  3445.   assert(x)
  3446.   assert(y)
  3447.   assert(color)
  3448.  
  3449.   out.setCursorPos(x, y)
  3450.   out.setBackgroundColor(color)
  3451.   out.write(" ")
  3452. end
  3453.  
  3454. function drawBox(x, y, width, height, color)
  3455.   out.setBackgroundColor(color)
  3456.  
  3457.   for row = 1, height do
  3458.     out.setCursorPos(x, y + row - 1)
  3459.     out.write(string.rep(" ", width))
  3460.    
  3461.     for col = x, width do
  3462.       if (WindowBuffer[col] and WindowBuffer[col][y + row - 1]) then
  3463.         WindowBuffer[col][y + row - 1].draw = true
  3464.       end
  3465.     end
  3466.   end
  3467. end
  3468.  
  3469. -- Displays the text with red background colour.
  3470. function drawSimpleButton(x, y, text, backgroundColor, textColor)
  3471.   assert(x)
  3472.   assert(y)
  3473.   assert(text)
  3474.  
  3475.   backgroundColor = backgroundColor or ObjectColors.Button.default
  3476.   textColor = textColor or ObjectColors.Button.text
  3477.  
  3478.   out.setCursorPos(x, y)
  3479.   out.setBackgroundColor(backgroundColor)
  3480.   out.setTextColor(textColor)
  3481.   out.write(text)
  3482.  
  3483.   out.setBackgroundColor(ObjectColors.background)
  3484.   out.setTextColor(ObjectColors.text)
  3485. end
  3486.  
  3487. -- Displays the default buttons.
  3488. function drawDefaultButtons()
  3489.   local window = getCurrentWindow()
  3490.   local button
  3491.  
  3492.   if (window.showRefreshButton) then
  3493.     button = DefaultButtons.refresh
  3494.     drawSimpleButton(button.left, button.top, button.text, ObjectColors.DefaultButtons.default, ObjectColors.DefaultButtons.text) -- Refresh
  3495.   end
  3496.  
  3497.   if (window.showBackButton) then
  3498.     button = DefaultButtons.back
  3499.     drawSimpleButton(button.left, button.top, button.text, ObjectColors.DefaultButtons.default, ObjectColors.DefaultButtons.text) -- Back
  3500.   end
  3501.  
  3502.   if (window.showQuitButton ~= false) then
  3503.     button = DefaultButtons.quit
  3504.     drawSimpleButton(button.left, button.top, button.text, ObjectColors.DefaultButtons.Quit.default, ObjectColors.DefaultButtons.Quit.text) -- Quit
  3505.   end
  3506.  
  3507.   button = DefaultButtons.options
  3508.   if (button.required()) then
  3509.     drawSimpleButton(button.left, button.top, button.text, ObjectColors.DefaultButtons.default, ObjectColors.DefaultButtons.text) -- Options
  3510.   end
  3511. end
  3512.  
  3513. -- Displays all objects of the window with the
  3514. -- ID windowID on the screen and changes the
  3515. -- variable "currentWindow".
  3516. function drawWindow(windowID)
  3517.   local windowContainer = getWindowContainer()
  3518.  
  3519.   if windowID and windowContainer and windowContainer.Children[windowID] then
  3520.     currentWindow = windowID
  3521.   elseif currentWindow then
  3522.     windowID = currentWindow
  3523.   elseif not showCustomWindow then
  3524.     if Windows.startupWindow then
  3525.       currentWindow = Windows.startupWindow
  3526.     elseif (#Windows.Children ~= 0) then -- Automatically set a startup window.
  3527.       for windowName, window in pairs(Windows.Children) do
  3528.         Windows.startupWindow = windowName
  3529.         currentWindow = windowName
  3530.         break
  3531.       end
  3532.     else -- We have no windows, create the "Main" window.
  3533.       Objects.Container.Window.create("Main")
  3534.       Windows.Children.Main.isStartupWindow = true
  3535.       currentWindow = "Main"
  3536.     end
  3537.   else
  3538.     error("No current window for custom window container " .. showCustomWindow .. " found.", 1)
  3539.   end
  3540.  
  3541.   log("Drawing window \"" .. currentWindow .. "\" with custom window \"" .. tostring(showCustomWindow) .. "\".", "INFO")
  3542.   local windowObject = getCurrentWindow()
  3543.  
  3544.   if windowObject then
  3545.     WindowBuffer = Objects.Container.get(windowObject)
  3546.     WindowBuffer:draw()
  3547.     drawDefaultButtons()
  3548.   end
  3549. end
  3550.  
  3551. -- >> Input Processing
  3552.  
  3553. -- Works just like os.pullEvent but it only
  3554. -- returns custom Graffiti events.
  3555. -- Recommended for API usage.
  3556. function pullEvent(requestedEvent)
  3557.   if not eventTypeExists(requestedEvent) then
  3558.     clearScreen()
  3559.     print("Event type " .. tostring(requestedEvent) .. " is invalid!")
  3560.     print()
  3561.     print("Available event types:")
  3562.    
  3563.     for _, event in pairs(EventTypes) do
  3564.       print("  " .. event)
  3565.     end
  3566.    
  3567.     error()
  3568.   end
  3569.  
  3570.   local finished = false
  3571.   local event, params
  3572.  
  3573.   while not finished do
  3574.     event, params = getInput()
  3575.    
  3576.     if event then
  3577.       if (requestedEvent ~= nil) then
  3578.         if (requestedEvent == event or event == "quit") then
  3579.           finished = true
  3580.         end
  3581.       else
  3582.         finished = true
  3583.       end
  3584.     end
  3585.   end
  3586.  
  3587.   return event, unpack(params)
  3588. end
  3589.  
  3590. function getInput()
  3591.   log("getInput", "FUNC")
  3592.  
  3593.   local finished = false
  3594.   local event, params
  3595.   local x, y, mouseButton, dragged = getCursorInput(true)
  3596.   log("Received mouse input. X: " .. x .. ", Y: " .. y .. ", button: " .. mouseButton .. ", dragged: " .. tostring(dragged) .. ".", "INFO")
  3597.  
  3598.   if not dragged then
  3599.     if (defaultButtonPressed("quit", x, y)) then
  3600.       log("Quit pressed")
  3601.       quit = true
  3602.     elseif (defaultButtonPressed("refresh", x, y)) then
  3603.       log("Refresh pressed")
  3604.       drawWindow()
  3605.       finished = true
  3606.     elseif (defaultButtonPressed("back", x, y)) then
  3607.       log("Back pressed")
  3608.      
  3609.       local windowObject = getCurrentWindow()
  3610.      
  3611.       if (windowObject.parent ~= nil) then
  3612.         drawWindow(windowObject.parent)
  3613.         finished = true
  3614.       else
  3615.         finished = true
  3616.       end
  3617.     end
  3618.   end
  3619.  
  3620.   if finished then
  3621.     return nil
  3622.   elseif quit then
  3623.     return "quit", { "Graffiti" } -- Used for the API
  3624.   end
  3625.  
  3626.   local param
  3627.   local path = WindowBuffer.bufferTable[x][y].path
  3628.  
  3629.   if path and #path > 0 then
  3630.     local object = Path.getObject(path)
  3631.     local clickX, clickY = Path.getRelativePos(path, x, y)
  3632.     local modX, modY = Objects.getPosModifier(object)
  3633.     clickX, clickY = clickX + (modX * -1), clickY + (modY * -1)
  3634.     clickX, clickY = clickX - object.x + 1, clickY - object.y + 1
  3635.    
  3636.     if dragged then
  3637.       event, params = Objects.drag(object, clickX, clickY)
  3638.     else
  3639.       event, params = Objects.click(object, clickX, clickY)
  3640.     end
  3641.   end
  3642.  
  3643.   return event, params
  3644. end
  3645.  
  3646. -- Shows the message on the computer for debugging.
  3647. function debugMessage(message)
  3648.   if outIsTerm then
  3649.     error("Can't display a debug message on a computer!")
  3650.   end
  3651.  
  3652.   print(message)
  3653. end
  3654.  
  3655. function splitAt(self, delimiter)
  3656.   delimiterPos = string.find(self, delimiter)
  3657.   left = string.sub(self, 1, delimiterPos - 1)
  3658.   right = string.sub(self, delimiterPos + #delimiter)
  3659.  
  3660.   return left, right
  3661. end
  3662.  
  3663. -- >>> Custom Windows
  3664.  
  3665. CustomWindows.init = function()
  3666.   log("Initializing custom windows...", "INFO")
  3667.  
  3668.   for windowName, window in pairs(CustomWindows) do
  3669.     if (type(window) == "table" and window.init) then
  3670.       log("Initializing custom window \"" .. windowName .. "\".", "INFO")
  3671.       window.init()
  3672.     end
  3673.   end
  3674.  
  3675.   showCustomWindow = nil
  3676.   currentWindow = nil
  3677. end
  3678.  
  3679. CustomWindows.draw = function(containerName, windowName)
  3680.   assert(containerName)
  3681.   assert(windowName)
  3682.  
  3683.   log("Drawing custom window \"" .. windowName .. "\" from container \"" .. containerName .. "\".", "INFO")
  3684.  
  3685.   showCustomWindow = containerName
  3686.   drawWindow(windowName)
  3687.   local result = nil
  3688.  
  3689.   while result == nil do
  3690.     local event, params = getInput()
  3691.    
  3692.     if (event == "button_clicked") then
  3693.       local objID = params[1]
  3694.      
  3695.       if (objID == "apply") then
  3696.         result = true
  3697.       elseif (objID == "cancel") then
  3698.         result = false
  3699.       end
  3700.     elseif (event == "quit") then
  3701.       result = true
  3702.       quit = false
  3703.     end
  3704.   end
  3705.  
  3706.   clearScreen()
  3707.   showCustomWindow = nil
  3708.  
  3709.   return result
  3710. end
  3711.  
  3712. -- >> Editor Windows
  3713. CustomWindows.Editor = {}
  3714.  
  3715. CustomWindows.Editor.init = function()
  3716.   showCustomWindow = "Editor"
  3717.   CustomWindows.Editor.Children = {}
  3718.  
  3719.   -- Main window
  3720.   Objects.Container.Window.create("Main")
  3721.   currentWindow = "Main"
  3722.  
  3723.   Objects.createCustom("ScrollView", 2, 2, { objID="windowListContainer", width=30, height=maxY-2, scrollXEnabled=true, scrollYEnabled=true } )
  3724.   Objects.createCustom("List", 4, 4, { objID="WindowList", items={}, load=CustomFunctions.Editor.getWindowList, isMultiselect=false } )
  3725.  
  3726.   Objects.createCustom("Panel", 34, 2, { objID="newWindow", width=15, height=7 } )
  3727.   Objects.createCustom("Input", 36, 4, { objID="newWindow", message="Enter a name for the new window." } )
  3728.   Objects.createCustom("Button", 36, 6, { objID="newWindow", width=8, height=1, text="New", funcType="function" } )
  3729.   Objects.createCustom("Button", 36, 7, { objID="renameWindow", width=8, height=1, text="Rename", funcType="function" } )
  3730.  
  3731.   Objects.createCustom("Button", 34, 10, { objID="editWindow", width=17, height=1, text="Edit", funcType="function" } )
  3732.   Objects.createCustom("Button", 34, 12, { objID="deleteWindow", width=17, height=1, text="Delete", funcType="function" } )
  3733.   Objects.createCustom("Button", 34, 14, { objID="setParent", width=17, height=1, text="Set parent", funcType="function" } )
  3734.   Objects.createCustom("Button", 34, 16, { objID="setStartup", width=17, height=1, text="Startup window", funcType="function" } )
  3735. end
  3736.  
  3737. CustomFunctions.Editor = {}
  3738.  
  3739. CustomFunctions.Editor.getWindowList = function()
  3740.   local ret = {}
  3741.  
  3742.   for key, value in pairs(Windows.Children) do
  3743.     table.insert(ret, key)
  3744.   end
  3745.  
  3746.   table.sort(ret)
  3747.  
  3748.   return ret
  3749. end
  3750.  
  3751. CustomFunctions.Editor.getWindowListObject = function()
  3752.   return CustomWindows.Editor.Children.Main.Children[1].Children[1]
  3753. end
  3754.  
  3755. CustomFunctions.Editor.editLastWindow = function()
  3756.  
  3757.   if not lastWindow then
  3758.     if Windows.startupWindow then
  3759.       showCustomWindow = nil
  3760.       lastWindow = Windows.startupWindow
  3761.     else
  3762.       return
  3763.     end
  3764.   end
  3765.  
  3766.   changeButtonColor = false
  3767.   drawWindow(lastWindow)
  3768. end
  3769.  
  3770. -- Creates a new window. The user has to enter the window name in the computer.
  3771. CustomFunctions.Editor.newWindow = function()
  3772.   local windowName = ObjectData.Input["newWindow"]
  3773.   local list = CustomFunctions.Editor.getWindowListObject()
  3774.  
  3775.   if (windowName == nil or windowName == "") then
  3776.     return
  3777.   elseif (Windows.Children[windowName] == nil) then
  3778.     showCustomWindow = nil
  3779.     Objects.Container.Window.create(windowName)
  3780.     changeButtonColor = false
  3781.     showCustomWindow = "Editor"
  3782.   end
  3783.  
  3784.   ListEditorList = list.Items
  3785.   ObjectData.Input["newWindow"] = ""
  3786.   drawWindow()
  3787. end
  3788.  
  3789. CustomFunctions.Editor.renameWindow = function()
  3790.   local newWindowName = ObjectData.Input["newWindow"]
  3791.   local list = CustomFunctions.Editor.getWindowListObject()
  3792.   local key = Objects.List.getFirstSelectedValue(list)
  3793.  
  3794.   if (value and not table.contains(windowList, newWindowName)) then
  3795.     local oldWindowName = value
  3796.    
  3797.     Files.rename({ Files.Project.subDir, currentProject }, oldWindowName .. Files.Project.extension, newWindowName .. Files.Project.extension)
  3798.     Windows.Children[newWindowName] = Windows.Children[oldWindowName]
  3799.     Windows.Children[oldWindowName] = nil
  3800.   end
  3801.  
  3802.   drawWindow()
  3803. end
  3804.  
  3805. -- Edits the first selected window.
  3806. CustomFunctions.Editor.editWindow = function()
  3807.   local list = CustomFunctions.Editor.getWindowListObject()
  3808.   local key = Objects.List.getFirstSelectedKey(list)
  3809.  
  3810.   if not key then
  3811.     return
  3812.   end
  3813.  
  3814.   showCustomWindow = nil
  3815.   lastWindow = list.Items[key]
  3816.   changeButtonColor = false
  3817.   drawWindow(lastWindow)
  3818. end
  3819.  
  3820. -- Deletes the selected window(s).
  3821. CustomFunctions.Editor.deleteWindow = function()
  3822.   local list = CustomFunctions.Editor.getWindowListObject()
  3823.  
  3824.   for key, selected in pairs(ObjectData.List[list.objID]) do
  3825.     if (selected == true) then
  3826.       local windowName = list.Items[key]
  3827.       Files.remove(Files.Project.subDir, fs.combine(currentProject, windowName .. Files.Project.extension))
  3828.       Windows.Children[windowName] = nil
  3829.     end
  3830.   end
  3831.  
  3832.   drawWindow()
  3833.   ListEditorList = list.Items
  3834. end
  3835.  
  3836. -- Sets the first selected window as the one that
  3837. -- should be displayed on startup.
  3838. CustomFunctions.Editor.setStartup = function()
  3839.   local list = CustomFunctions.Editor.getWindowListObject()
  3840.   local key = Objects.List.getFirstSelectedKey(list)
  3841.   local selectedWindowName = list.Items[key]
  3842.  
  3843.   for windowName, window in pairs(Windows.Children) do
  3844.     window.isStartupWindow = (windowName == selectedWindowName)
  3845.   end
  3846. end
  3847.  
  3848. -- Let's the user define the parent-attribute of the current window.
  3849. CustomFunctions.Editor.setParent = function()
  3850.   local list = CustomFunctions.Editor.getWindowListObject()
  3851.   local selected = Objects.List.getFirstSelectedKey(list)
  3852.   local buffer = Objects.Line.get("vertical", list.height, ObjectColors.background, {1, 99})
  3853.  
  3854.   local x, y, mouseButton = getCursorInput()
  3855.   local modX, modY = Objects.getPosModifier(list)
  3856.   x, y = x + (modX * -1), y + (modY * -1)
  3857.   local selectedParent = y - list.absoluteY + 1
  3858.  
  3859.   if (selectedParent >= 1 and selectedParent <= list.height) then -- Clicked inside the list.
  3860.     if (selectedParent ~= selected) then -- Selected parentWindow is not selected window.
  3861.       Windows.Children[list.Items[selected]].parent = list.Items[selectedParent]
  3862.     end
  3863.   end
  3864.  
  3865.   for i = 1, list.height do
  3866.     drawPixel(1, i + list.absoluteY - 1, ObjectColors.background)
  3867.   end
  3868. end
  3869.  
  3870. -- >> List Editor Windows
  3871. CustomWindows.ListEditor = {}
  3872.  
  3873. CustomWindows.ListEditor.init = function()
  3874.   showCustomWindow = "ListEditor"
  3875.   CustomWindows.ListEditor.Children = {}
  3876.  
  3877.   Objects.Container.Window.create("Main")
  3878.   CustomWindows.ListEditor.Children["Main"].showQuitButton = false
  3879.   currentWindow = "Main"
  3880.  
  3881.   Objects.createCustom("ScrollView", 2, 2, { objID="listContainer", width=22, height=maxY - 3, scrollXEnabled=true, scrollYEnabled=true } )
  3882.   Objects.createCustom("List", 4, 4, { objID="itemList", isMultiselect=true, items={}, load=CustomFunctions.ListEditor.getListEditorItems } )
  3883.  
  3884.   Objects.createCustom("Panel", 26, 2, { objID="newEntryPanel", width=16, height=7 } )
  3885.   Objects.createCustom("Input", 28, 4, { objID="newEntry" } )
  3886.   Objects.createCustom("Button", 28, 6, { objID="newEntry", width=11, height=1, text="New", funcType="function" } )
  3887.   Objects.createCustom("Button", 28, 7, { objID="renameEntry", width=11, height=1, text="Rename", funcType="function" } )
  3888.  
  3889.   Objects.createCustom("Text", 26, 11, { objID="editEntryText",   text="Edit entries" } )
  3890.   Objects.createCustom("Panel", 26, 12, { objID="editEntryPanel", width=16, height=8 } )
  3891.   Objects.createCustom("Button", 28, 14, { objID="moveEntryUp",   width=11, height=1, text="Move up", funcType="function" } )
  3892.   Objects.createCustom("Button", 28, 15, { objID="moveEntryDown", width=11, height=1, text="Move down", funcType="function" } )
  3893.   Objects.createCustom("Button", 28, 17, { objID="removeEntry",   width=11, height=1, text="Remove", funcType="function" } )
  3894.  
  3895.   Objects.createCustom("Button", 43, 12, { objID="apply", width=8, height=3, text="OK", funcType="function" } )
  3896.   Objects.createCustom("Button", 43, 16, { objID="cancel", width=8, height=3, text="Cancel", funcType="function" } )
  3897. end
  3898.  
  3899. CustomFunctions.ListEditor = {}
  3900.  
  3901. CustomFunctions.ListEditor.getListEditorItems = function()
  3902.   local tempList = {}
  3903.  
  3904.   for _, value in ipairs(ListEditorList) do
  3905.     table.insert(tempList, value)
  3906.   end
  3907.  
  3908.   return tempList
  3909. end
  3910.  
  3911. CustomFunctions.ListEditor.newEntry = function()
  3912.   local input = ObjectData.Input["newEntry"]
  3913.  
  3914.   if (input == nil or input == "") then
  3915.     return
  3916.   end
  3917.  
  3918.   table.insert(ListEditorList, input)
  3919.   ObjectData.Input["newEntry"] = nil
  3920.   drawWindow()
  3921. end
  3922.  
  3923. CustomFunctions.ListEditor.renameEntry = function()
  3924.   local selectedItems = ObjectData.List["itemList"]
  3925.   local input = ObjectData.Input["newEntry"]
  3926.  
  3927.   if (input == nil or input == "") then
  3928.     return
  3929.   end
  3930.  
  3931.   for key, value in pairs(selectedItems) do
  3932.     if value then -- Item is selected
  3933.       ListEditorList[key] = input
  3934.     end
  3935.   end
  3936.  
  3937.   ObjectData.Input["newEntry"] = nil
  3938.   drawWindow()
  3939. end
  3940.  
  3941. CustomFunctions.ListEditor.moveEntryUp = function()
  3942.   if not ObjectData.List["itemList"] then
  3943.     return
  3944.   end
  3945.  
  3946.   local lastIndex = nil
  3947.   local selectedItems = ObjectData.List["itemList"]
  3948.   local lock = nil
  3949.  
  3950.   for key, value in pairs(ListEditorList) do
  3951.     local selected = selectedItems[key] == true
  3952.    
  3953.     if lastIndex then
  3954.       if selected then
  3955.         if (lock ~= lastIndex) then
  3956.           selectedItems[lastIndex], selectedItems[key] =
  3957.               selectedItems[key], selectedItems[lastIndex]
  3958.          
  3959.           ListEditorList[lastIndex], ListEditorList[key] =
  3960.               ListEditorList[key], ListEditorList[lastIndex]
  3961.         else
  3962.           lock = key
  3963.         end
  3964.       end
  3965.     elseif selected then
  3966.       lock = key
  3967.     end
  3968.    
  3969.     lastIndex = key
  3970.   end
  3971.  
  3972.   drawWindow()
  3973. end
  3974.  
  3975. CustomFunctions.ListEditor.moveEntryDown = function()
  3976.   if not ObjectData.List["itemList"] then
  3977.     return
  3978.   end
  3979.  
  3980.   local lastIndex = nil
  3981.   local selectedItems = ObjectData.List["itemList"]
  3982.   local lock = nil
  3983.  
  3984.   for key = #ListEditorList, 1, -1 do
  3985.     local selected = selectedItems[key] == true
  3986.    
  3987.     if lastIndex then
  3988.       if selected then
  3989.         if (lock ~= lastIndex) then
  3990.           selectedItems[lastIndex], selectedItems[key] =
  3991.               selectedItems[key], selectedItems[lastIndex]
  3992.          
  3993.           ListEditorList[lastIndex], ListEditorList[key] =
  3994.               ListEditorList[key], ListEditorList[lastIndex]
  3995.         else
  3996.           lock = key
  3997.         end
  3998.       end
  3999.     elseif selected then
  4000.       lock = key
  4001.     end
  4002.    
  4003.     lastIndex = key
  4004.   end
  4005.  
  4006.   drawWindow()
  4007. end
  4008.  
  4009. CustomFunctions.ListEditor.removeEntry = function()
  4010.   local selectedItems = ObjectData.List["itemList"]
  4011.   local ret = {}
  4012.  
  4013.   for key, value in pairs(ListEditorList) do
  4014.     local selected = (selectedItems[key] == true)
  4015.    
  4016.     if not selected then
  4017.       table.insert(ret, ListEditorList[key])
  4018.     end
  4019.   end
  4020.  
  4021.   ObjectData.List["itemList"] = {}
  4022.   ListEditorList = ret
  4023.  
  4024.   drawWindow()
  4025. end
  4026.  
  4027. -- >> Setup Windows
  4028. CustomWindows.Setup = {}
  4029.  
  4030. CustomWindows.Setup.init = function()
  4031.   showCustomWindow = "Setup"
  4032.   CustomWindows.Setup.Children = {}
  4033.  
  4034.   Objects.Container.Window.create("Main")
  4035.   currentWindow = "Main"
  4036.  
  4037.   Objects.createCustom("Text", 2, 2, { objID="welcomeText", text=Text.setupText1 } )
  4038.   Objects.createCustom("Text", 2, 3, { objID="welcomeText", text=Text.setupText2 } )
  4039.   Objects.createCustom("Text", 2, 5, { objID="welcomeText", text=Text.setupText3 } )
  4040.   Objects.createCustom("Text", 2, 6, { objID="welcomeText", text=Text.setupText4 } )
  4041.   Objects.createCustom("Text", 2, 7, { objID="welcomeText", text=Text.setupText5 } )
  4042.  
  4043.   Objects.createCustom("Button", 2, 9, { objID="nextWindow", text=Text.next, width=6, height=3, funcType="switch", window="Setup1" } )
  4044.   Objects.createCustom("Button", 2, 13, { objID="cancel", text=Text.skipSetup, width=36, height=3, funcType="function" } )
  4045.  
  4046.   Objects.Container.Window.create("Setup1")
  4047.   CustomWindows.Setup.Children["Setup1"].showBackButton = true
  4048.   CustomWindows.Setup.Children["Setup1"].parent = "Main"
  4049.   currentWindow = "Setup1"
  4050.  
  4051.   Objects.createCustom("Text", 2, 2, { objID="chooseSettings", text=Text.chooseSettings1 } )
  4052.  
  4053.   Objects.createCustom("Text", 2, 4, { objID="chooseLanguage", text=Text.language .. ":" } )
  4054.   Objects.createCustom("ScrollView", 2, 5, { objID="Language", width=14, height=maxY - 6, scrollXEnabled=true, scrollYEnabled=true } )
  4055.   CustomWindows.Setup.languageList = Objects.createCustom("List", 4, 7, { objID="languages", load=CustomFunctions.Setup.getLanguageList } )
  4056.  
  4057.   Objects.createCustom("Text", 17, 4, { objID="chooseColorTheme", text=Text.colorTheme .. ":" } )
  4058.   Objects.createCustom("ScrollView", 17, 5, { objID="colorTheme", width=14, height=maxY - 6, scrollXEnabled=true, scrollYEnabled=true } )
  4059.   CustomWindows.Setup.colorThemeList = Objects.createCustom("List", 19, 7, { objID="colorTheme", load=CustomFunctions.Setup.getColorThemeList } )
  4060.  
  4061.   Objects.createCustom("RadioButton", 34, 4, { objID="showDataFolder", text=Text.showDataFolder, defaultIsChecked=true } )
  4062.   Objects.createCustom("RadioButton", 34, 6, { objID="hideDataFolder", text=Text.hideDataFolder } )
  4063.  
  4064.   Objects.createCustom("Button", 34, 16, { objID="apply", text=Text.finished, width=15, height=3, funcType="function" } )
  4065. end
  4066.  
  4067. CustomFunctions.Setup = {}
  4068.  
  4069. CustomFunctions.Setup.getLanguageList = function()
  4070.   local ret = {}
  4071.  
  4072.   for key, value in pairs(Languages) do
  4073.     table.insert(ret, key)
  4074.   end
  4075.  
  4076.   table.sort(ret)
  4077.  
  4078.   return ret
  4079. end
  4080.  
  4081. CustomFunctions.Setup.getColorThemeList = function()
  4082.   local ret = {}
  4083.  
  4084.   for key, value in pairs(ColorThemes) do
  4085.     table.insert(ret, key)
  4086.   end
  4087.  
  4088.   table.sort(ret)
  4089.  
  4090.   return ret
  4091. end
  4092.  
  4093. -- Runs the setup.
  4094. function runSetup(useDefaultSettings)
  4095.   if not useDefaultSettings then
  4096.     initDefaultButtons()
  4097.     CustomWindows.Setup.init()
  4098.     CustomWindows.draw("Setup", "Main")
  4099.   end
  4100.  
  4101.   local languageListObject = CustomWindows.Setup.languageList
  4102.   local colorThemeListObject = CustomWindows.Setup.colorThemeList
  4103.  
  4104.   local selectedLanguage = Objects.List.getFirstSelectedValue(languageListObject)
  4105.   local selectedColorTheme = Objects.List.getFirstSelectedValue(colorThemeListObject)
  4106.  
  4107.   if selectedLanguage and Languages[selectedLanguage] then
  4108.     Settings.language = selectedLanguage
  4109.   end
  4110.  
  4111.   if (selectedColorTheme and ColorThemes[selectedColorTheme]) then
  4112.     Settings.colorTheme = selectedColorTheme
  4113.   end
  4114.  
  4115.   Settings.hideDataFolder = ObjectData.RadioButton["showDataFolder"] == true or not ObjectData.RadioButton["hideDataFolder"]
  4116.  
  4117.   dataFolderPath = fs.combine(root, (Settings.hideDataFolder and ".GraffitiData" or "GraffitiData"))
  4118.  
  4119.   if not fs.exists(dataFolderPath) then
  4120.     fs.makeDir(dataFolderPath)
  4121.   end
  4122.  
  4123.   Files.save()
  4124. end
  4125.  
  4126. -- Shows lines marking the top left part of an
  4127. -- object as well as well as pixels displaying
  4128. -- the alignment of an object.
  4129. function drawAlignmentLines(object, left, top, right, bottom)
  4130.   local color = ObjectColors.Editor.marker
  4131.   local moveX, moveY = Objects.getMovePos(object)
  4132.  
  4133.   -- Draw the lines.
  4134.   Objects.Line.draw(left - 1, moveY, "left", left - 2, color) -- left
  4135.   Objects.Line.draw(moveX, top -1, "up", top - 2, color) -- up
  4136.   Objects.Line.draw(right + 1, moveY, "right", maxX - (right + 1), color) -- right
  4137.   Objects.Line.draw(moveX, bottom + 1, "down", maxY - (bottom + 1), color) -- down
  4138.  
  4139.   -- Display the alignment-pixels.
  4140.   horizontalAlignment = object.horizontalAlignment
  4141.   verticalAlignment = object.verticalAlignment
  4142.  
  4143.   if (horizontalAlignment == "left" or horizontalAlignment == "stretch") then -- left
  4144.     drawPixel(1, moveY, ObjectColors["Editor"].alignmentTrue)
  4145.   else
  4146.     drawPixel(1, moveY, ObjectColors["Editor"].alignmentFalse)
  4147.   end
  4148.  
  4149.   if (horizontalAlignment == "right" or horizontalAlignment == "stretch") then -- right
  4150.     drawPixel(maxX, moveY, ObjectColors["Editor"].alignmentTrue)
  4151.   else
  4152.     drawPixel(maxX, moveY, ObjectColors["Editor"].alignmentFalse)
  4153.   end
  4154.  
  4155.   if (verticalAlignment == "top" or verticalAlignment == "stretch") then -- top
  4156.     drawPixel(moveX, 1, ObjectColors["Editor"].alignmentTrue)
  4157.   else
  4158.     drawPixel(moveX, 1, ObjectColors["Editor"].alignmentFalse)
  4159.   end
  4160.  
  4161.   if (verticalAlignment == "bottom" or verticalAlignment == "stretch") then -- bottom
  4162.     drawPixel(moveX, maxY, ObjectColors["Editor"].alignmentTrue)
  4163.   else
  4164.     drawPixel(moveX, maxY, ObjectColors["Editor"].alignmentFalse)
  4165.   end
  4166.  
  4167.   out.setBackgroundColor(ObjectColors.background)
  4168. end
  4169.  
  4170. -- Returns the values of horizontalAlignment and
  4171. -- verticalAlignment depending which sides are set
  4172. -- to true.
  4173. function getAlignment(left, top, right, bottom)
  4174.   local retHorizontal, retVertical = "left", "top"
  4175.  
  4176.   if right then
  4177.     if left then
  4178.       retHorizontal = "stretch"
  4179.     else
  4180.       retHorizontal = "right"
  4181.     end
  4182.   else
  4183.     retHorizontal = "left"
  4184.   end
  4185.  
  4186.   if bottom then
  4187.     if top then
  4188.       retVertical = "stretch"
  4189.     else
  4190.       retVertical = "bottom"
  4191.     end
  4192.   else
  4193.     retVertical = "top"
  4194.   end
  4195.  
  4196.   return retHorizontal, retVertical
  4197. end
  4198.  
  4199. function markSelectedObject()
  4200.   if not selectedObject then
  4201.     return
  4202.   end
  4203.  
  4204.   Objects.draw(selectedObject, nil, true) -- Draw the object with its markers.
  4205.  
  4206.   local moveX, moveY = Objects.getMovePos(selectedObject)
  4207.   drawPixel(moveX, moveY, (currentEditorAction == "Move" and not selectedObjectDragged) and ObjectColors.Editor.active or ObjectColors.Editor.move)
  4208.  
  4209.   if selectedObject.canScale then
  4210.     local scaleX, scaleY = Objects.getScalePos(selectedObject)
  4211.     drawPixel(scaleX, scaleY, (currentEditorAction == "Scale" and not selectedObjectDragged) and ObjectColors.Editor.active or ObjectColors.Editor.scale)
  4212.   end
  4213.  
  4214.   out.setBackgroundColor(ObjectColors.background)
  4215. end
  4216.  
  4217. -- Let's the user delete an object or change its attributes depending on the current edit-mode.
  4218. function editObject(object, x, y, dragged)
  4219.   assert(object)
  4220.   log("Editing " .. object.objType .. ", ID: " .. object.objID .. ", x: " .. x .. ", y: " .. y .. ", dragged: " .. tostring(dragged) .. ".")
  4221.  
  4222.   local relX, relY = Path.getRelativePos(object.path, x, y)
  4223.   local modX, modY = Objects.getPosModifier(object)
  4224.   local left, top, right, bottom = Objects.getDimensions(object)
  4225.   left, top, right, bottom = left + modX, top + modY, right + modX, bottom + modY
  4226.  
  4227.   local moveX, moveY = Objects.getMovePos(object)
  4228.  
  4229.   local scaleX, scaleY
  4230.   if object.canScale then
  4231.     scaleX, scaleY = Objects.getScalePos(object)
  4232.   end
  4233.  
  4234.   if currentEditorAction then
  4235.     if (currentEditorAction == "Move") then
  4236.       addX = x - moveX
  4237.       addY = y - moveY
  4238.       Objects.move(object, addX, addY)
  4239.     elseif (currentEditorAction == "Scale") then
  4240.       Objects.scale(object, relX + (modX * -1), relY + (modY * -1))
  4241.     else
  4242.       error("Unknown editor action \"" .. tostring(currentEditorAction) .. "\".", 1)
  4243.     end
  4244.    
  4245.     if dragged then
  4246.       selectedObjectDragged = true
  4247.     else
  4248.       currentEditorAction = nil
  4249.     end
  4250.   else
  4251.     if (x == moveX and y == moveY) then
  4252.       currentEditorAction = "Move"
  4253.     elseif (object.canScale and x == scaleX and y == scaleY) then
  4254.       currentEditorAction = "Scale"
  4255.     else
  4256.       relX, relY = relX - object.x + 1, relY - object.y + 1
  4257.      
  4258.       if not (Objects.editorClick(object, relX + modX, relY + modY)) then
  4259.         if (not outIsTerm and Objects.Selector.draw(x, y, RightClickActions)) then
  4260.           if (selectedItem == "Attributes") then
  4261.             Objects.editAttributes(object)
  4262.           elseif (selectedItem == "Delete") then
  4263.             Objects.remove(object, "Delete")
  4264.             drawWindow()
  4265.           end
  4266.         end
  4267.       end
  4268.     end
  4269.   end
  4270.  
  4271.   out.setBackgroundColor(ObjectColors.background)
  4272.   drawWindow()
  4273. end
  4274.  
  4275. function markVariables(container)
  4276.   assert(container)
  4277.  
  4278.   for _, object in pairs(container.Children) do
  4279.     if (object.isContainer) then
  4280.       markVariables(object)
  4281.     elseif (object.objType == "Variable") then
  4282.       local x, y = Objects.getAbsolutePos(object)
  4283.       drawPixel(x, y, ObjectColors.Editor.marker)
  4284.       out.setBackgroundColor(ObjectColors.background)
  4285.     end
  4286.   end
  4287. end
  4288.  
  4289. function markDefaultButtons()
  4290.   local window = getCurrentWindow()
  4291.  
  4292.   -- refresh button
  4293.   local refresh = DefaultButtons.refresh
  4294.   out.setCursorPos(refresh.left, refresh.top)
  4295.   if (window.showRefreshButton) then
  4296.     out.setBackgroundColor(ObjectColors.Button.default)
  4297.     out.write(refresh.text)
  4298.   else
  4299.     out.setBackgroundColor(ObjectColors.Editor.marker)
  4300.     out.write(string.rep(" ", #refresh.text))
  4301.   end
  4302.  
  4303.   -- back button
  4304.   local back = DefaultButtons.back
  4305.   out.setCursorPos(back.left, back.top)
  4306.   if (window.showBackButton) then
  4307.     out.setBackgroundColor(ObjectColors.Button.default)
  4308.     out.write(back.text)
  4309.   else
  4310.     out.setBackgroundColor(ObjectColors.Editor.marker)
  4311.     out.write(string.rep(" ", #back.text))
  4312.   end
  4313.  
  4314.   out.setBackgroundColor(ObjectColors.background)
  4315. end
  4316.  
  4317. function getEditorInput()
  4318.   log("getEditorInput", "FUNC")
  4319.  
  4320.   local event
  4321.   local x, y, mouseButton, dragged
  4322.  
  4323.   if showCustomWindow ~= "Editor" then
  4324.     drawWindow()
  4325.     markDefaultButtons()
  4326.     markSelectedObject()
  4327.    
  4328.     event = getAnyInput()
  4329.    
  4330.     if (event.eventType == "mouse") then
  4331.       x, y, mouseButton, dragged = event.x, event.y, event.mouseButton, event.dragged
  4332.       log("Received mouse input. X: " .. x .. ", Y: " .. y .. ", button: " .. mouseButton .. ".")
  4333.     end
  4334.   end
  4335.  
  4336.   if (showCustomWindow ~= "Editor" and event.eventType == "key") then
  4337.     callShortcut(event.key)
  4338.   elseif (showCustomWindow == "Editor" or defaultButtonPressed("options", x, y)) then
  4339.     selectedObject = nil
  4340.     showCustomWindow = "Editor"
  4341.     drawWindow("Main")
  4342.    
  4343.     while showCustomWindow == "Editor" and not quit do
  4344.       getInput()
  4345.     end
  4346.   elseif (not dragged and defaultButtonPressed("quit", x, y)) then
  4347.     quit = true
  4348.   elseif (not dragged and defaultButtonPressed("refresh", x, y)) then
  4349.     Windows.Children[currentWindow].showRefreshButton = not Windows.Children[currentWindow].showRefreshButton
  4350.   elseif (not dragged and defaultButtonPressed("back", x, y)) then
  4351.     Windows.Children[currentWindow].showBackButton = not Windows.Children[currentWindow].showBackButton
  4352.   else
  4353.     if dragged then
  4354.       if (selectedObject == nil) then
  4355.         return
  4356.       else
  4357.         editObject(selectedObject, x, y, dragged)
  4358.       end
  4359.     else
  4360.       if selectedObjectDragged then
  4361.         selectedObjectDragged = false
  4362.         currentEditorAction = nil
  4363.       end
  4364.      
  4365.       local container = getCurrentWindow()
  4366.       local path = WindowBuffer.bufferTable[x][y].path
  4367.      
  4368.       if (path == nil or #path == 0) then -- No object touched.
  4369.         if selectedObject then
  4370.           if currentEditorAction then
  4371.             editObject(selectedObject, x, y)
  4372.           end
  4373.          
  4374.           selectedObject = nil
  4375.         else
  4376.           drawPixel(x, y, ObjectColors.Editor.new)
  4377.           if (Objects.Selector.draw(x, y, ObjectTypes)) then -- Draw selector for new object.
  4378.             Objects.create(selectedItem, x, y)
  4379.           end
  4380.         end
  4381.       else
  4382.         local object = Path.getObject(path)
  4383.        
  4384.         if (mouseButton == 1) then
  4385.           if (selectedObject and Objects.isClicked(selectedObject, x, y)) then
  4386.             editObject(selectedObject, x, y, dragged)
  4387.           else
  4388.             selectedObject = object
  4389.           end
  4390.         else
  4391.           if selectedObject then
  4392.             selectedObject = nil
  4393.           elseif (Objects.Selector.draw(x, y, RightClickActions)) then
  4394.             if (selectedItem == "Attributes") then
  4395.               Objects.editAttributes(object)
  4396.             elseif (selectedItem == "Delete") then
  4397.               Objects.remove(object, "Delete")
  4398.             end
  4399.           end
  4400.         end
  4401.       end
  4402.     end
  4403.   end
  4404. end
  4405.  
  4406. -- Runs Graffiti in editMode.
  4407. function windowEditor()
  4408.   editMode = true
  4409.  
  4410.   showCustomWindow = "Editor"
  4411.  
  4412.   while not quit do
  4413.     getEditorInput()
  4414.   end
  4415. end
  4416.  
  4417. function round(number)
  4418.   assert(number)
  4419.   comma = number % 1
  4420.   if comma < 0.5 then
  4421.     ret = math.floor(number)
  4422.   else
  4423.     ret = math.ceil(number)
  4424.   end
  4425.  
  4426.   return ret
  4427. end
  4428.  
  4429. function printInfo()
  4430.   print()
  4431.   print(version)
  4432.   print("Author: Encreedem")
  4433.   print()
  4434.   print("Param(s):")
  4435.   print("info - Shows some info about the program... but I guess you know that already.")
  4436.   print("edit - Starts the program in edit-mode.")
  4437.   print()
  4438.   print("Visit the CC-forums or my YouTube channel (Encreedem CP) for news and help.")
  4439. end
  4440.  
  4441. -- Gets called when Graffiti gets the argument "test"
  4442. function testMethod()
  4443.   error("Nothing to test...", 2)
  4444. end
  4445.  
  4446. -- >>> initialization
  4447.  
  4448. -- Sets the "startupWindow" variable to the first
  4449. -- window with enabled "isStartupWindow" attribute.
  4450. function getStartupWindow()
  4451.   for windowName, window in pairs(Windows.Children) do
  4452.     if window.isStartupWindow then
  4453.       startupWindow = windowName
  4454.       return
  4455.     end
  4456.   end
  4457. end
  4458.  
  4459. -- Initializes the default buttons.
  4460. -- (Quit, Back, Refresh, Options)
  4461. function initDefaultButtons()
  4462.   DefaultButtons.quit = {
  4463.     text=Text.quit,
  4464.     left=maxX - string.len(Text.quit) + 1,
  4465.     top=1,
  4466.     right=maxX,
  4467.     bottom=1,
  4468.     required = function()
  4469.       return getCurrentWindow().showQuitButton ~= false
  4470.     end
  4471.   }
  4472.  
  4473.   DefaultButtons.back = {
  4474.     text = Text.back,
  4475.     left = 1,
  4476.     top = 1,
  4477.     right = string.len(Text.back),
  4478.     bottom = 1,
  4479.     required = function()
  4480.       return getCurrentWindow().showBackButton
  4481.     end
  4482.   }
  4483.  
  4484.   DefaultButtons.refresh = {
  4485.     text = Text.refresh,
  4486.     left = maxX - string.len(Text.refresh) + 1,
  4487.     top = maxY,
  4488.     right = maxX,
  4489.     bottom = maxY,
  4490.     required = function()
  4491.       return (getCurrentWindow().showRefreshButton or (editMode and not showEdtorOptions))
  4492.     end
  4493.   }
  4494.  
  4495.   DefaultButtons.options = {
  4496.     text = Text.options,
  4497.     left = 1,
  4498.     top = maxY,
  4499.     right = string.len(Text.options),
  4500.     bottom = maxY,
  4501.     required=function()
  4502.       return (editMode and showCustomWindow ~= "Editor")
  4503.     end
  4504.   }
  4505. end
  4506.  
  4507. -- Initializes all windows to allow Graffiti to
  4508. -- use them as if they were objects.
  4509. function initWindows()
  4510.   for _, window in pairs(Windows.Children) do
  4511.     window.width, window.height = maxX, maxY
  4512.   end
  4513. end
  4514.  
  4515. -- Tells the user that the monitor or computer
  4516. -- doesn't support colors.
  4517. function showColorWarning()
  4518.   out.clear()
  4519.   out.setCursorPos(2, 2)
  4520.   out.write("This computer/monitor does not support colors!")
  4521.  
  4522.   local state = 0
  4523.   local move = "I don't know this move!"
  4524.   local finished = false
  4525.   while not finished and not quit do
  4526.     out.setCursorPos(1, 4)
  4527.     out.clearLine()
  4528.     out.setCursorPos(2, 4)
  4529.    
  4530.     if (state == 0) then
  4531.       move = "<( \" <) <( \" <) <( \" <)"
  4532.     elseif (state == 1 or state == 3 or state == 5) then
  4533.       move = "  (^\"^)   (^\"^)   (^\"^)"
  4534.     elseif (state == 2) then
  4535.       move = "  (> \" )> (> \" )> (> \" )>"
  4536.     elseif (state == 4) then
  4537.       move = " (> \" )><( \" )><( \" <)"
  4538.     elseif (state == 6) then
  4539.       move = "<( \" <) (>\"<) (> \" )>"
  4540.     elseif (state == 7) then
  4541.       move = " (v''v) (v''v) (v''v)"
  4542.     else
  4543.       error("Unable to show you that you need an advanced computer/monitor in a fancy way!")
  4544.     end
  4545.    
  4546.     out.write(move)
  4547.     state = (state + 1) % 8
  4548.     os.sleep(0.25)
  4549.   end
  4550. end
  4551.  
  4552. -- Checks if the monitor on monitorSide exists and wraps it into "monitor".
  4553. function getOutput()
  4554.   if (monitor == nil and outIsTerm == false) then
  4555.     local monitorFound = false
  4556.     for _, side in pairs(Sides) do
  4557.       if (peripheral.getType(side) == "monitor") then
  4558.         monitor = peripheral.wrap(side)
  4559.         monitorFound = true
  4560.         out = monitor
  4561.         outIsTerm = false
  4562.       end
  4563.     end
  4564.    
  4565.     if not monitorFound then
  4566.       out = term
  4567.       outIsTerm = true
  4568.     end
  4569.   elseif outIsTerm then
  4570.     out = term
  4571.   else
  4572.     out = monitor
  4573.   end
  4574. end
  4575.  
  4576. -- Initializes Graffitis' variables.
  4577. function init()
  4578.   getOutput()
  4579.  
  4580.   maxX, maxY = out.getSize()
  4581.   if (maxX < 16 or maxY < 10) then -- smaller than 2x2
  4582.     print("Screen too small! You need at least 2x2 monitors!")
  4583.     return false
  4584.   elseif not out.isColor() then
  4585.     parallel.waitForAny(showColorWarning, getKeyInput)
  4586.     clearScreen()
  4587.     return false
  4588.   end
  4589.  
  4590.   isAPI = (shell == nil)
  4591.  
  4592.   initDone = true
  4593.   return true
  4594. end
  4595.  
  4596. function checkArgs()
  4597.   doCall = main
  4598.   arg = Args[1]
  4599.  
  4600.   if (arg ~= nil) then
  4601.     if (arg == "edit") then
  4602.       doCall = windowEditor
  4603.     elseif (arg == "info") then
  4604.       doCall = printInfo
  4605.     elseif (arg == "term") then
  4606.       outIsTerm = true
  4607.     elseif (arg == "test") then
  4608.       doCall = testMethod
  4609.     end
  4610.   end
  4611.  
  4612.   doCall()
  4613. end
  4614.  
  4615. -- Calls the "getInput" function until the user presses the quit-button.
  4616. function main()
  4617.   drawWindow(startupWindow)
  4618.  
  4619.   while not quit do
  4620.     getInput()
  4621.   end
  4622. end
  4623.  
  4624. if init() then
  4625.   Files.init()
  4626.   Files.clear(Files.Log.subDir, Files.Log.name)
  4627.   logFileLoaded = true
  4628.   log("Graffiti initialized.")
  4629.  
  4630.   getStartupWindow()
  4631.   initWindows()
  4632.   CustomWindows.init()
  4633.   initDefaultButtons()
  4634.   Objects.init()
  4635.  
  4636.   if not isAPI then
  4637.     checkArgs() -- Calls the "main" function. Everything below happens after closing Graffiti.
  4638.    
  4639.     -- Closing Program
  4640.     if editMode and saveAfterQuit then
  4641.       Files.save()
  4642.     end
  4643.    
  4644.     out.setBackgroundColor(colors.black)
  4645.     out.setTextColor(colors.white)
  4646.     out.clear()
  4647.     out.setCursorPos(1, 1)
  4648.   else
  4649.     log("Graffiti loaded as an API.")
  4650.   end
  4651. else
  4652.   error("Graffiti Initialization failed!")
  4653. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement