Trystan_C_C

Gmail-Client

Nov 25th, 2012
1,327
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 35.74 KB | None | 0 0
  1. --[[
  2.         Gmail-Client            PaymentOption
  3.                                 Google on BlackWolfCC
  4.                                 23 November 2012
  5.                                
  6.         Mail client script to correspond allow users
  7.         to send mail to a server and thus to other
  8.         clients with file attachments. Unreceived
  9.         mail due to inactivity will be stored in the
  10.         server and will be resent every ten seconds if
  11.         the proper confirmation message is not sent to
  12.         the server.
  13.        
  14.         Current Version: 24 November 2012: Incomplete menu system. Spaghetti
  15.                                            coded some of the menus and their
  16.                                            corresponding drawing functions.
  17.                                            Networking system modified to send
  18.                                            messages as their table bodies as
  19.                                            opposed to strings.
  20.        
  21.         Basic Model:
  22.                 - Client -> Server -> Receiver -> Server(Confirmation)
  23.                 -> Client(Confirmation) or Stored to resend until
  24.                 confirmation received.
  25.                
  26.         Message Model:
  27.                 {sender = (number), receiver = (number),
  28.                  message = (table), subject = (string),
  29.                  attachment = {code = (string)}}
  30.                 - Attachments will only be sent if the attachment table
  31.                   exists within the message table provided.
  32.                 - The whole message will be in a table, and therefore
  33.                   will be serialized and unserialized upon sending and
  34.                   receiving.
  35. ]]--
  36.  
  37. -- Variables --
  38. local serverID = 0 -- The computer from which we will send and receive mail from.
  39. local receivedMailDirectory = "Gmail-Client/Received_Mail" -- The location in
  40.                                                            -- which received mail
  41.                                                            -- is stored.
  42. local screenWidth, screenHeight = term.getSize() -- The x and y dimensions of the
  43.                                                  -- screen we are currently on.
  44. local confirmationFormat = "ConfirmedReception" -- The message that must be
  45.                                                 -- sent to the server to
  46.                                                 -- confirm a successful message
  47.                                                 -- send.
  48. -- Variables --
  49.  
  50.  
  51.  
  52. -- Rednet Functions --
  53. -- Opens or close any modem found on this computer. An error is thrown if
  54. -- no modem on the computer was found.
  55. -- Params : open - A boolean value that determines whehter or not the modem is
  56. --                 opened or closed.
  57. -- Returns: nil
  58. function openOrCloseModem(open)
  59.         for sideIndex, side in pairs(rs.getSides()) do
  60.                 if peripheral.isPresent(side) and peripheral.getType(side) == "modem" then
  61.                         if open then
  62.                                 rednet.open(side)
  63.                         else
  64.                                 rednet.close(side)
  65.                         end
  66.                         return
  67.                 end
  68.         end
  69.        
  70.         error("No modem found. Aborting.")
  71. end
  72.  
  73. -- Sends a mail message to the server in a serialized table format.
  74. -- Params : receiverID - The receiver for this message.
  75. --          subjectString - The subject for the message.
  76. --          messageString - The contents of the message.
  77. --          attachmentString - The contents of a file to send as an attachment.
  78. -- Returns: nil
  79. function sendMailMessage(receiverID, subjectString, messageLines, attachmentString)
  80.         local attachment = nil
  81.         if attachmentString then
  82.                 attachment = {code = attachmentString}
  83.         end
  84.         local unserializedMessage = {sender = os.getComputerID(), receiver = receiverID, message = messageLines, subject = subjectString,
  85.                                      attachment}
  86.         --if unserializedMessage.sender == os.getComputerID() then os.shutdown() end
  87.         local serializedMessage = textutils.serialize(unserializedMessage)
  88.        
  89.         rednet.send(serverID, serializedMessage)
  90. end
  91. -- Rednet Functions --
  92.  
  93.  
  94.  
  95. -- File Functions --
  96. -- Saves the given message in its SERIALIZED format into the saved messages
  97. -- directory with the format: <sender>_<subject>_#ofTimesRepeated
  98. -- with #ofTimesRepeated being the number of times that the same message
  99. -- appears in the saved messages directory. However, when the list of messages
  100. -- is displayed, the messages will not be listed differently, but they will
  101. -- be separate entities.
  102. -- Params : serializedMessage - The string version of the message that has
  103. --                              been serialized.
  104. -- Returns: nil
  105. function saveSerializedMessage(serializedMessage)
  106.         -- Respond to the server with a confirmation message.
  107.         rednet.send(serverID, confirmationFormat)
  108.         -- Get the number of times the message has been repeated in the saved
  109.         -- messages directory.
  110.         local unserializedMessage = textutils.unserialize(serializedMessage)
  111.         local newSavedMessageName = unserializedMessage.sender .. '_' .. unserializedMessage.subject
  112.         newSavedMessageName = newSavedMessageName .. '_' ..  getNumberOfTimesFileAppearsInDirectory(receivedMailDirectory, newSavedMessageName)
  113.        
  114.         -- Get a file handle for the new saved message, then write the serialized
  115.         -- message to aforementioned file and close the handle.
  116.         local fileHandle = fs.open(receivedMailDirectory .. '/' .. newSavedMessageName, 'w')
  117.         fileHandle.write(serializedMessage)
  118.         fileHandle.close()
  119. end
  120.  
  121. -- Loads the message with the given path into an unserialized form and returns it.
  122. -- Params : messageName - The name of the message in the saved messages directory;
  123. --                        NOT THE PATH!
  124. -- Returns: nil
  125. function loadMessageFromSavedMessagesDirectory(messageName)
  126.         local fileHandle = fs.open(receivedMailDirectory .. '/' .. messageName)
  127.         local unserializedMessage = textutils.unserialize(fileHandle.readAll())
  128.         fileHandle.close()
  129.        
  130.         return unserializedMessage
  131. end
  132.  
  133. -- Checks the amount of times the same file name appears in a directory.
  134. -- Params : directory, fileName - The directory and file name to check.
  135. -- Returns: timesAppeared - The number of times the same file name appears.
  136. function getNumberOfTimesFileAppearsInDirectory(directory, fileName)
  137.         local timesAppeared = 0
  138.         for index, item in pairs(fs.list(directory)) do
  139.                 if item:find(fileName) then
  140.                         timesAppeared = timesAppeared + 1
  141.                 end
  142.         end
  143.        
  144.         return timesAppeared
  145. end
  146.  
  147. -- File Functions --
  148.  
  149.  
  150. -- Input Functions --
  151. -- Gets input on the line given and will display the input typed until
  152. -- the character length given. Upon reaching the character limit, the function
  153. -- will proceed to scroll the input to the right. No cursor movement back
  154. -- and forth within the input line is supportted.
  155. -- This function also handles rednet messages that are received during input.
  156. -- Params : xPos, yPos - The x and y coordinates to begin grabbing input.
  157. --          charachterLimit - The number of characters that can be entered
  158. --                            before the function will begin scrolling.
  159. --          currentLine - The line to start with in the case that you want
  160. --                        to edit a string.
  161. -- Returns: line - The string of input entered.
  162. function getInputAtPosAndScrollRight(xPos, yPos, characterLimit, currentLine)
  163.         term.setCursorBlink(true)
  164.         local scroll = 0
  165.         local line = tostring(currentLine) or ""
  166.        
  167.         local function redraw()
  168.                 term.setCursorPos(xPos, yPos)
  169.                 term.write(string.rep(' ', line:len() + 1))
  170.                
  171.                 term.setCursorPos(xPos, yPos)
  172.                 if line:len() - 1 > characterLimit then
  173.                         scroll = line:len() - characterLimit
  174.                         term.write(line:sub(scroll, line:len()))
  175.                         return
  176.                 end
  177.                
  178.                 term.write(line)
  179.         end
  180.        
  181.         -- Main input loop.
  182.         while true do
  183.                 redraw()
  184.                
  185.                 local event, key, p2, p3 = os.pullEvent()
  186.                 -- If the event was a character typed, then add the character
  187.                 -- typed to the input string.
  188.                 if event == "char" then
  189.                         line = line .. key
  190.                 elseif event == "key" then
  191.                         -- If the enter key was pressed, then
  192.                         -- return the string retrieved from the user.
  193.                         if key == keys.enter then
  194.                                 term.setCursorBlink(false)
  195.                                 return line
  196.                         -- If the backspace key was pressed, then back up the
  197.                         -- input line by 1 charachter via substring.
  198.                         elseif key == keys.backspace then
  199.                                 line = line:sub(1, line:len() - 1)
  200.                         end
  201.                 -- If the event was a message and it was from the server, then
  202.                 -- handle it as a serialized mail message.
  203.                 elseif event == "rednet_message" and key == serverID then
  204.                         saveSerializedMessage(p2)
  205.                 end
  206.         end
  207. end
  208.  
  209. -- Gets input over multiple lines. Uses a table to store the input at each line.
  210. -- The input is scrollable so more than the selected amount of lines can be
  211. -- written. However, only a certain amount of characters is limited per line;
  212. -- if this limit is reached the cursor will not reset, but the user will just
  213. -- not be permitted input any more text without jumping to the next line.
  214. -- This function also handles rednet messages that are received during input.
  215. -- Params : xPos, yPos - The x and y coordinates to begin getting input at.
  216. --          maxLines - The maximum number of lines to be typed before the
  217. --                     screen will begin scrolling.
  218. --          lines - The lines to edit if you do not want to have an empty input.
  219. -- Returns: lines - The table of lines that are retrieved via input.
  220. function getInputAtPosInLines(xPos, yPos, maxLines, lines)
  221.         term.setCursorBlink(true)
  222.        
  223.         local maxLines = maxLines or screenHeight
  224.         local lines = lines or {""} -- The table that will contain all of the lines retrieved.
  225.         local currentLine = 1 -- The current line that we are on in the liens table.
  226.         local maxLineLength = screenWidth - (xPos + 1) -- The maximum amount of characters
  227.                                                        -- that can be on one line.
  228.         local scroll = 0 -- The offset of the lines to be displayed.
  229.        
  230.         -- Redraws all of the text from the xPos and yPos using a loop to
  231.         -- draw each of the lines on a separate line.
  232.         local function redrawLines()
  233.                 -- Clear the space allotted.
  234.                 for line = yPos, maxLines + yPos do
  235.                         term.setCursorPos(xPos, line)
  236.                         term.write(string.rep(' ', maxLineLength))
  237.                 end
  238.                
  239.                 -- If the amount of lines is greater than can be displayed, then
  240.                 -- scroll the screen.
  241.                 if #lines - maxLines > 0 then
  242.                         scroll = #lines - maxLines
  243.                 end
  244.                 -- If the current line is not greater than the screen, the we
  245.                 -- don't need to scroll it.
  246.                 if currentLine <= maxLines then
  247.                         scroll = 0
  248.                 end
  249.                
  250.                 for index = 1 + scroll, #lines do
  251.                         term.setCursorPos(xPos, index - scroll + yPos - 1)
  252.                         term.write(lines[index])
  253.                 end
  254.         end
  255.        
  256.         -- Main loop
  257.         while true do
  258.                 -- Redraw the current input and position the cursor properly.
  259.                 redrawLines()
  260.                 term.setCursorPos(lines[currentLine]:len() + xPos, currentLine - scroll + yPos - 1)
  261.                
  262.                 local event, key = os.pullEvent()
  263.                 -- If the event pulled was a character, then simply add the character
  264.                 -- typed to the end of the current line.
  265.                 if event == "char" then
  266.                         -- Make sure the current line doesn't already have the
  267.                         -- maximum amount of characters in it.
  268.                         if lines[currentLine]:len() < maxLineLength then
  269.                                 lines[currentLine] = lines[currentLine] .. key
  270.                         end
  271.                 elseif event == "key" then
  272.                         -- Enter.
  273.                         if key == keys["return"] then
  274.                                 -- If the enter key was pressed, then insert a blank line
  275.                                 -- into the current line if there are lines below the
  276.                                 -- current one.
  277.                                 if currentLine < #lines then
  278.                                         table.insert(lines, currentLine + 1, "")
  279.                                         currentLine = currentLine + 1
  280.                                 -- However, if the enter key was pressed and there are
  281.                                 -- no lines below this one, then jump to the next line
  282.                                 -- and make a new line in the lines table.
  283.                                 else
  284.                                         table.insert(lines, "")
  285.                                         currentLine = currentLine + 1
  286.                                 end
  287.                         -- If the backspace key was pressed, then get a substring
  288.                         -- of the current line with the ending character chopped off,
  289.                         -- then make that substring the actual line.
  290.                         elseif key == keys["backspace"] then
  291.                                 -- If the current line has no more characters in it, then delete the
  292.                                 -- current line and back up the line once.
  293.                                 if lines[currentLine]:len() == 0 and currentLine > 1 then
  294.                                         table.remove(lines, currentLine)
  295.                                         currentLine = currentLine - 1
  296.                                 else
  297.                                         lines[currentLine] = lines[currentLine]:sub(1, lines[currentLine]:len() - 1)
  298.                                 end
  299.                         -- Up key.
  300.                         elseif key == keys["up"] then
  301.                                 -- If we're not on the first line, then bump up the
  302.                                 -- current line by one line.
  303.                                 if currentLine > 1 then
  304.                                         currentLine = currentLine - 1
  305.                                 end
  306.                         -- Down key.
  307.                         elseif key == keys["down"] then
  308.                                 -- If we're not on the last line, then bump down the
  309.                                 -- current line by one line.
  310.                                 if currentLine < #lines then
  311.                                         currentLine = currentLine + 1
  312.                                 end
  313.                         -- If the end key was pressed, then return the current lines.
  314.                         elseif key == keys["end"] then
  315.                                 term.setCursorBlink(false)
  316.                                 return lines
  317.                         end
  318.                 end
  319.         end
  320.        
  321. end
  322.  
  323. -- Sets the current serverID for the script.
  324. -- Params : newServerID - The new ID for the server to be set as.
  325. -- Returns: nil
  326. function setServerID(newServerID)
  327.         serverID = newServerID
  328. end
  329. -- Input Functions --
  330.  
  331.  
  332.  
  333. -- UI Functions --
  334. -- Clears the screen with the given color. If no color is provided, then black
  335. -- will be used instead.
  336. -- Params : color - The color to clear the screen with.
  337. -- Returns: nil
  338. function clearScreen(color)
  339.         term.setBackgroundColor(color or colors.black)
  340.         term.clear()
  341.         term.setCursorPos(1, 1)
  342. end
  343.  
  344. -- Gets a new serverID from the user to receive mail from.
  345. -- Params : nil
  346. -- Returns: nil
  347. function getNewServerID()
  348.         local item = {label = "Change Server", xPos = 1, yPos = 7, associatedFunction = getNewServerID, centered = true, color = colors.blue}
  349.         local xPos = screenWidth/2 - item.label:len()/2
  350.         term.setCursorPos(xPos, item.yPos)
  351.         term.write(string.rep(' ', item.label:len()))
  352.        
  353.         term.setTextColor(colors.orange)
  354.         local newServerID = getInputAtPosAndScrollRight(xPos, item.yPos, item.label:len())
  355.         newServerID = tonumber(newServerID)
  356.        
  357.         -- Make sure the new serverID entered was a valid number.
  358.         if type(newServerID) == "number" then
  359.                 setServerID(newServerID)
  360.         end
  361. end
  362.  
  363. -- Prints the current serverID that this computer is connected to. If no
  364. -- position is provided, then this information will be printed in the bottom
  365. -- right hand corner of the screen.
  366. -- Params : xPos, yPos - The x and y coordinates for this information to be drawn.
  367. -- Returns: nil
  368. function printCurrentServerID(xPos, yPos)
  369.         local serverInfo = tostring(serverID)
  370.         if serverInfo:len() > 4 then
  371.                 serverInfo = "Server: " .. serverInfo:sub(1, 1) .. "..."
  372.         else
  373.                 serverInfo = "Server: " .. serverInfo
  374.         end
  375.        
  376.         if not xPos and not yPos then
  377.                 xPos = screenWidth - serverInfo:len() + 1
  378.                 yPos = screenHeight
  379.         end
  380.         term.setCursorPos(xPos, yPos)
  381.        
  382.         term.setTextColor(colors.red)
  383.         term.write(serverInfo)
  384. end
  385.  
  386. -- Prints the amount of mail in the saved mail directory. This acts as a similar
  387. -- method of displaying the amount of unread mail. However, whether or not
  388. -- the mail was read is not taken into account when displaying this information.
  389. -- If no position is provided, then htis information will be displayed in the
  390. -- lower left hand corner of the screen.
  391. -- Params : xPos, yPos - The x and y coordinates for this information to be drawn.
  392. -- Returns: nil
  393. function printNumberOfMessagesInMailDirectory(xPos, yPos)
  394.         if not xPos and not yPos then
  395.                 xPos = 1
  396.                 yPos = screenHeight
  397.         end
  398.         term.setCursorPos(xPos, yPos)
  399.        
  400.         term.setTextColor(colors.red)
  401.         term.write("Mail: " .. #fs.list(receivedMailDirectory))
  402. end
  403.  
  404. -- Prints the google logo to the position located. If no position was
  405. -- provided, then it will default to the top center of the screen.
  406. -- Params : xPos, yPos - The x and y coordinates for the logo to be drawn.
  407. -- Returns: nil
  408. function printLogo(xPos, yPos)
  409.         if not xPos and not yPos then
  410.                 xPos = screenWidth/2 - ("Google"):len()/2
  411.                 yPos = 1
  412.         end
  413.         term.setCursorPos(xPos, yPos)
  414.        
  415.         local logoColors = {colors.blue, colors.red, colors.yellow, colors.blue,
  416.                             colors.green, colors.red} -- The colors for the logo
  417.                                                       -- that we're printing.
  418.         local logo = "Google" -- The logo to be printed.
  419.         for index = 1, logo:len() do
  420.                 term.setTextColor(logoColors[index])
  421.                 term.write(logo:sub(index, index))
  422.         end
  423. end
  424.  
  425. -- Prints the Gmail-Client title to the top of the screen at the given coordinates,
  426. -- or the center of the screen on line two by default.
  427. -- Params : xPos, yPos - The x and y coordinates of the title to be drawn.
  428. -- Returns: nil
  429. function printTitle(xPos, yPos)
  430.         if not xPos and not yPos then
  431.                 xPos = screenWidth/2 - ("Gmail-Client"):len()/2
  432.                 yPos = 2
  433.         end
  434.         term.setCursorPos(xPos, yPos)
  435.        
  436.         local titleColors = {colors.blue, colors.red, colors.yellow, colors.blue,
  437.                             colors.green, colors.red, colors.blue, colors.red,
  438.                             colors.yellow, colors.blue, colors.green, colors.red}
  439.                             -- The colors for the title to be printed.
  440.         local title = "Gmail-Client"
  441.         for index = 1, title:len() do
  442.                 term.setTextColor(titleColors[index])
  443.                 term.write(title:sub(index, index))
  444.         end
  445. end
  446.  
  447. -- Prints the current time in the Minecraft world. By default, however, this
  448. -- information will be written at the bottom right of the screen.
  449. -- Params : xPos, yPos - The x and y coordinates for this information to be
  450. --                       drawn at.
  451. -- Returns: nil
  452. function printCurrentTimeInMinecraft(xPos, yPos)
  453.         local time = textutils.formatTime(os.time(), true)
  454.         if not xPos and not yPos then
  455.                 xPos = screenWidth/2 - ("Current Time: " .. time):len()/2
  456.                 yPos = screenHeight
  457.         end
  458.         term.setCursorPos(xPos, yPos)
  459.         term.write("Current Time: " .. time)
  460. end
  461.  
  462. -- Draws the menu table given. Each entry in a menu table should have the
  463. -- following format:
  464. -- menu[n] = {label = (string), xPos = (number), yPos = (number), associatedFunction = (function), centered = (boolean), color = (number)}.
  465. -- Colors MUST be assigned to each entry or else the function will throw an error.
  466. -- Params : menu - The menu table to be drawn.
  467. -- Returns: nil
  468. function drawMenu(menu)
  469.         for index, item in ipairs(menu) do
  470.                 if item.centered then
  471.                         term.setCursorPos(screenWidth/2 - item.label:len()/2, item.yPos)
  472.                 else
  473.                         term.setCursorPos(item.xPos, item.yPos)
  474.                 end
  475.                 term.setTextColor(item.color)
  476.                 term.write(item.label)
  477.         end
  478. end
  479.  
  480. -- Handles and prints the main screen for the client. This includes drawing
  481. -- the main screen and handling clicking of the interface.
  482. -- Params : nil
  483. -- Returns: nil
  484. function drawAndHandleMainScreen()
  485.         local mainMenuTable = {      -- The main menu screen for the client script.
  486.         [1] = {label = "Compose Mail", xPos = 1, yPos = 5, associatedFunction = drawAndHandleComposeMailScreen, centered = true, color = colors.green},
  487.         [2] = {label = "Read Mail", xPos = 1, yPos = 6, associatedFunction = drawAndHandleReadScreen, centered = true, color = colors.red},
  488.         [3] = {label = "Change Server", xPos = 1, yPos = 7, associatedFunction = getNewServerID, centered = true, color = colors.blue},
  489.         [4] = {label = "Exit", xPos = 1, yPos = 8, associatedFunction = exitProgram, centered = true, color = colors.yellow}
  490.         }
  491.        
  492.         -- Draw the menu and all of the extra information to go along with it.
  493.         while true do
  494.                 clearScreen(colors.white)
  495.                 printLogo()
  496.                 printTitle()
  497.                 printCurrentTimeInMinecraft()
  498.                 printNumberOfMessagesInMailDirectory()
  499.                 printCurrentServerID()
  500.                 drawMenu(mainMenuTable)
  501.                
  502.                 local updateTimer = os.startTimer(1) -- The amount of time before
  503.                                                      -- the next screen update.
  504.                 local event, incomingID, incomingSerializedMessage, p3 = os.pullEvent()
  505.                
  506.                 -- If the event triggered was a mouse click, then handle the click
  507.                 -- accordingly by executing the function associated with the item clicked.
  508.                 if event == "mouse_click" then
  509.                         for index, item in ipairs(mainMenuTable) do
  510.                                 local item_xPos = (item.centered == true) and (screenWidth/2 - item.label:len()/2) or item.xPos
  511.                                 if checkIfClickWasOnTextAtPos(incomingSerializedMessage, p3, item_xPos, item.yPos, item.label) then
  512.                                         if item.associatedFunction and item.associatedFunction() == false then
  513.                                                 -- Exit the program because the
  514.                                                 -- only function that will return
  515.                                                 -- a value in the main screen
  516.                                                 -- will be false by the exit
  517.                                                 -- option.
  518.                                                 return false
  519.                                         end
  520.                                 end
  521.                         end
  522.                 -- If the event was a rednet message, then send a confirmation
  523.                 -- message to the server, then store the message in
  524.                 -- the saved messages directory, then alert the user that a
  525.                 -- message was received.
  526.                 elseif event == "rednet_message" and incomingID == serverID then
  527.                         saveSerializedMessage(incomingSerializedMessage)
  528.                 end
  529.         end
  530. end
  531.  
  532. -- Prints the receiver field for the compose mail screen where the user can
  533. -- enter a receiver to send a mail message to.
  534. -- Params : nil
  535. -- Returns: nil
  536. function printReceiverFieldForComposeMailScreen(receiverID, fieldWidth)
  537.         if receiverID and tostring(receiverID):len() > fieldWidth then
  538.                 receiverID = tostring(receiverID):sub(1, fieldWidth - 3) .. "..."
  539.         end
  540.        
  541.         term.setCursorPos(2, 3)
  542.         term.setTextColor(colors.red)
  543.         term.write("Send To   : " .. ((receiverID) and receiverID or ""))
  544.         term.setCursorPos(2, 4)
  545.        
  546.         term.setBackgroundColor(colors.lightGray)
  547.         term.write(string.rep(' ', screenWidth - 3))
  548.         term.setBackgroundColor(colors.white)
  549. end
  550.  
  551. -- Prints the subject field for the compose mail screen where the user can
  552. -- enter a subject for their mail message.
  553. -- Params : nil
  554. -- Returns: nil
  555. function printSubjectFieldForComposeMailScreen(subject, fieldWidth)
  556.         if subject and subject:len() > fieldWidth then
  557.                 subject = subject:sub(1, fieldWidth - 3) .. "..."
  558.         end
  559.        
  560.         term.setCursorPos(2, 5)
  561.         term.setTextColor(colors.red)
  562.         term.write("Subject   : " .. ((subject) and subject or ""))
  563.         term.setCursorPos(2, 6)
  564.        
  565.         term.setBackgroundColor(colors.lightGray)
  566.         term.write(string.rep(' ', screenWidth - 3))
  567.         term.setBackgroundColor(colors.white)
  568. end
  569.  
  570. -- Prints the attachment field for the compose mail screen where the user can
  571. -- enter a path to a file that they would like to attach to their mail message.
  572. -- Params : nil
  573. -- Returns: nil
  574. function printAttachmentFieldForComposeMailScreen(attachmentPath, fieldWidth)
  575.         if attachmentPath and attachmentPath:len() > fieldWidth then
  576.                 attachmentPath = attachmentPath:sub(1, fieldWidth - 3) .. "..."
  577.         end
  578.        
  579.         term.setCursorPos(2, 7)
  580.         term.setTextColor(colors.red)
  581.         term.write("Attachment: " .. ((attachmentPath) and attachmentPath or ""))
  582.         term.setCursorPos(2, 8)
  583.        
  584.         term.setBackgroundColor(colors.lightGray)
  585.         term.write(string.rep(' ', screenWidth - 3))
  586.         term.setBackgroundColor(colors.white)
  587. end
  588.  
  589. -- Prints the body field for the compose mail screen where the user can enter
  590. -- a body for their mail message.
  591. -- Params : nil
  592. -- Returns: nil
  593. function printBodyFieldForComposeMailScreen(body)
  594.         term.setCursorPos(2, 9)
  595.         term.setTextColor(colors.red)
  596.         term.write("Body (Below):")
  597.         term.setCursorPos(1, 10)
  598.        
  599.         term.setBackgroundColor(colors.lightGray)
  600.         term.write(string.rep(' ', screenWidth))
  601.         for line = 11, screenHeight - 1 do
  602.                 term.setCursorPos(1, line)
  603.                 term.write(' ')
  604.                 term.setCursorPos(screenWidth, line)
  605.                 term.write(' ')
  606.         end
  607.         term.setCursorPos(1, screenHeight)
  608.         term.write(" Exit | END to Stop Writing | ENTER to Send" .. string.rep(' ', screenWidth - (" Exit | END to Stop Writing | ENTER to Send"):len()))
  609.      
  610.         term.setTextColor(colors.red)
  611.         term.setBackgroundColor(colors.white)
  612.        
  613.         local maxLinesToBeDrawn = 1 -- The maximum number of lines from the body
  614.                                     -- table that should be drawn.
  615.         -- If the body is too large to fit the box, then only draw enough of
  616.         -- the body to fit the box.
  617.         if #body + 10 > screenHeight - 1 then
  618.                 maxLinesToBeDrawn = 8
  619.         elseif #body > 0 then
  620.                 maxLinesToBeDrawn = #body
  621.         end
  622.        
  623.         for index = 1, maxLinesToBeDrawn do
  624.                 term.setCursorPos(2, 11 + (index - 1))
  625.                 term.write(body[index])
  626.         end
  627. end
  628.  
  629. -- Handles and prints the compose mail screen for the client. This includes
  630. -- drawing the compose mail screen and handling clicking and typing in the
  631. -- interface.
  632. -- Params : nil
  633. -- Returns: nil
  634. function drawAndHandleComposeMailScreen()
  635.         local body = {""}
  636.         local receiverID = 0
  637.         local subject = ""
  638.         local attachmentPath = ""
  639.  
  640.         local fieldWidth = screenWidth - (" Send To   : "):len() - 1
  641.         while true do
  642.                 clearScreen(colors.white)
  643.                 printLogo()
  644.                 printTitle()
  645.                 printReceiverFieldForComposeMailScreen(receiverID, fieldWidth)
  646.                 printSubjectFieldForComposeMailScreen(subject, fieldWidth)
  647.                 printAttachmentFieldForComposeMailScreen(attachmentPath, fieldWidth)
  648.                 printBodyFieldForComposeMailScreen(body)
  649.                
  650.                 local event, button, xClickPos, yClickPos = os.pullEvent()
  651.                
  652.                 -- Handle incoming mail messages in the case we receive one
  653.                 -- whilst on this screen.
  654.                 if event == "rednet_message" and button == serverID then
  655.                         saveSerializedMessage(xClickPos)
  656.                 -- Handle mouse clicks by directing the user to type in the
  657.                 -- proper field.
  658.                 elseif event == "mouse_click" then
  659.                         -- Make sure the user did not click on the EXIT button at
  660.                         -- the bottom of the screen.
  661.                         if checkIfClickWasOnTextAtPos(xClickPos, yClickPos, 2, screenHeight, "EXIT") then
  662.                                 return
  663.                         -- If the user clicked on the SEND TO field, then allow the
  664.                         -- user to type in the SEND TO line.
  665.                         elseif checkIfClickWasOnTextAtPos(xClickPos, yClickPos, (" Send To   : "):len(), 3, string.rep(' ', fieldWidth)) then
  666.                                 local tempReceiverID = getInputAtPosAndScrollRight((" Send To   : "):len() + 1, 3, fieldWidth - 3, receiverID)
  667.                                 tempReceiverID = tonumber(tempReceiverID)
  668.                                 if type(tempReceiverID) == "number" then
  669.                                         receiverID = tempReceiverID
  670.                                 end
  671.                         -- If the user clicked on the SUBJECT field, then allow
  672.                         -- the user to type in the SUBJECT line. Since all
  673.                         -- of the fields are of the same width, we can reuse the
  674.                         -- previous line and simply changing the y coordinate.
  675.                         elseif checkIfClickWasOnTextAtPos(xClickPos, yClickPos, (" Send To   : "):len(), 5, string.rep(' ', fieldWidth)) then
  676.                                 subject = getInputAtPosAndScrollRight((" Send To   : "):len() + 1, 5, fieldWidth - 3, subject)
  677.                         -- If the user clicked on the ATTACHMENT field, then allow
  678.                         -- the user to type in the ATTACHMENT line. All of these
  679.                         -- fields can reuse the same click logic with a slight change
  680.                         -- to the yCoordinate of the line clicked.
  681.                         elseif checkIfClickWasOnTextAtPos(xClickPos, yClickPos, (" Send To   : "):len(), 7, string.rep(' ', fieldWidth)) then
  682.                                 attachmentPath = getInputAtPosAndScrollRight((" Send To   : "):len() + 1, 7, fieldWidth - 3, attachmentPath)
  683.                                 if not fs.exists(attachmentPath) or fs.isDir(attachmentPath) then
  684.                                         attachmentPath = "Invalid attachment path."
  685.                                 end
  686.                         -- If the user clicked on any line in the body, then go ahead
  687.                         -- and allow the user to edit the body of the message.
  688.                         else
  689.                                 for line = 11, screenHeight - 1 do
  690.                                         if xClickPos >= 2 and xClickPos <= screenWidth - 1 and yClickPos == line then
  691.                                                 body = getInputAtPosInLines(2, 11, screenHeight - 12, body)
  692.                                         end
  693.                                 end
  694.                         end
  695.                 -- Handle key events.
  696.                 elseif event == "key" then
  697.                         -- If the key was enter, then package up the message and send it.
  698.                         local attachmentString = nil
  699.                         if attachmentPath ~= "Invalid attachment path." and attachmentPath ~= "" then
  700.                                 local fileHandle = fs.open(attachmentPath, 'r')
  701.                                 local attachmentString = fileHandle.readAll()
  702.                                 fileHandle.close()
  703.                         end
  704.                         -- Send the message.
  705.                         sendMailMessage(receiverID, subject, body, attachmentString)
  706.                 end
  707.         end
  708. end
  709.  
  710. -- Exits the program and returns the terminal to a normal and useable state.
  711. -- Params : nil
  712. -- Returns: false
  713. function exitProgram()
  714.         return false
  715. end
  716. -- UI Functions --
  717.  
  718.  
  719.  
  720. -- Click Functions --
  721. -- Checks if the click at the given position is on top of the text at another
  722. -- given position provided.
  723. -- Params : xClickPos, yClickPos - The x and y coordinates of the click.
  724. --          xTextPos, yTextPos   - The x and y coordinates of the text.
  725. --          text                 - The text to be checked for clicks on.
  726. -- Returns: true or false - Whether or not the click was on the text provided.
  727. function checkIfClickWasOnTextAtPos(xClickPos, yClickPos, xTextPos, yTextPos, text)
  728.         if xClickPos >= xTextPos and xClickPos <= xTextPos + text:len() then
  729.                 if yClickPos == yTextPos then
  730.                         return true
  731.                 end
  732.         end
  733.        
  734.         return false
  735. end
  736. -- Click Functions --
  737.  
  738.  
  739.  
  740. -- Main Loop -------------------------------------------------------------------
  741. -- Setup all of the proper directories for the script to function properly.
  742. if not fs.isDir(receivedMailDirectory) then
  743.         fs.makeDir(receivedMailDirectory)
  744. end
  745. openOrCloseModem(true)
  746.  
  747. while true do
  748.         -- If the main screen function returns false, then we need to exit the
  749.         -- script and return to the shell.
  750.         if not drawAndHandleMainScreen() then
  751.                 openOrCloseModem(false)
  752.                 clearScreen(colors.black)
  753.                 term.setTextColor(colors.yellow)
  754.                 print(os.version())
  755.                
  756.                 return
  757.         end
  758. end
  759. --------------------------------------------------------------------------------
Advertisement
Add Comment
Please, Sign In to add comment