Advertisement
Encreedem

Graffiti v1.6.1

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