Advertisement
nitrogenfingers

WYSIwrite

Feb 11th, 2015
877
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 53.32 KB | None | 0 0
  1. --[[
  2.     WYSIwrite
  3.     A WYSIWYG text editor for printing documents in ComputerCraft
  4.  
  5.     Written by: Nitrogen Fingers
  6. ]]--
  7. doc = {}
  8.  
  9. --The width and height of the screen
  10. local w,h = term.getSize()
  11. --Whether or not the program is still running
  12. local finished = false
  13.  
  14. --The length allowed for an individual line
  15. local linelen = 25
  16. --The lines allowed for an individual page
  17. local pagelen = 21
  18.  
  19. --The currently viewed page
  20. local currpage = 1
  21. --The currently edited line
  22. local currline = 1
  23. --The currently index of the line edit
  24. local currind = 1
  25.  
  26. --The amount of scrolling applied to the page on the Y.
  27. local yscroll = 0
  28. --The drawing offset
  29. local _xoff,_yoff = math.floor(w/2 - linelen/2),4
  30. --Whether or not a scroll or page change has occurred
  31. local lscroll = true
  32. --Whether or not the player is currently in a drop down menu
  33. local inDropDown = false
  34.  
  35. --Whether or not whitespace characters are displayed
  36. local whitespaceVis = false
  37. --Whether or not a change has been made to the document since last save
  38. local sChange = false
  39. --The path currently being used to save to
  40. local sPath = nil
  41. --The file's name
  42. local sName = nil
  43.  
  44. --For me
  45. local testMode = false
  46. local _tro = false
  47.  
  48. function testPrint(_str)
  49.     if testMode then
  50.         term.setBackgroundColour(colours.white)
  51.         term.setTextColour(colours.red)
  52.         print(_str)
  53.         lscroll = true
  54.         _tro = true
  55.     end
  56. end
  57. function testWait()
  58.     if testMode and _tro then os.pullEvent("key") end
  59.     _tro = false
  60. end
  61.  
  62. --[[ Constructs an empty document that can be edited. Empty documents contain a
  63.      single page, with one line that cannot be deleted.
  64.      Parameters: none
  65.      Returns: none
  66. ]]--
  67. local function constructNewDocument(_sName, _sPath)
  68.     --pages
  69.     doc = {
  70.         --lines
  71.         {
  72.             ""
  73.         }
  74.     }
  75.     currpage = 1
  76.     currline = 1
  77.     currind = 1
  78.     paginateOverflow()
  79.     sPath = _sPath
  80.     sName = _sName
  81.     sChange = false
  82. end
  83.  
  84. --[[ Constructs a document from an existing source
  85.      Parameters: none
  86.      Returns: none
  87. ]]--
  88. local function loadDocument(_spath)
  89.     doc = {
  90.         { }
  91.     }
  92.     assert(fs.exists(_spath), _spath)
  93.     local _file = fs.open(_spath, "r")
  94.     local _line = _file.readLine()
  95.     while _line do
  96.         if #doc[#doc] < pagelen then
  97.             table.insert(doc[#doc], _line.."\n")
  98.         else
  99.             table.insert(doc, { })
  100.             table.insert(doc[#doc], _line.."\n")
  101.         end
  102.         currpage = #doc
  103.         currline = #doc[#doc]
  104.         --Sneaky newlines
  105.         _ol = _line
  106.         _line = _file.readLine()
  107.         if not _line then
  108.             doc[#doc][#doc[#doc]] = _ol
  109.         end
  110.         paginateOverflow()
  111.     end
  112.     currpage = 1
  113.     currline = 1
  114.     currind = 1
  115.     _file:close()
  116.     sChange = false
  117. end
  118.  
  119. --[[ "Depaginates" the current document into straight lines and saves the
  120.      output to a file location for standard viewing
  121.     Parameters: _spath:string= the location to save the file
  122.     Returns: none
  123. ]]--
  124. local function saveDocument()
  125.     local _lines = { "" }
  126.    
  127.     local _opi,_oli = currpage, currline
  128.     currpage = 1
  129.     currline = 0
  130.     while nextLine() do
  131.         local _cline = doc[currpage][currline]
  132.         if _cline:sub(#_cline) == "\n" then
  133.             _lines[#_lines] = _lines[#_lines].._cline:sub(1,#_cline-1)
  134.             table.insert(_lines, "")
  135.         else
  136.             _lines[#_lines] = _lines[#_lines].._cline
  137.         end
  138.     end
  139.     --Remove that empty 'newline' we include
  140.     if _lines[#_lines] == "" then table.remove(_lines, #_lines) end
  141.     local _file = fs.open(sPath, "w")
  142.     for i=1,#_lines do
  143.         _l = _lines[i]
  144.         _file.writeLine(_l)
  145.     end
  146.     _file.close()
  147.     currpage, currline = _opi, _oli
  148.     sChange = false
  149. end
  150.  
  151. --[[ Updates the document scroll to match the movement of the cursor.
  152.      Parameters: none
  153.      Returns: none
  154. ]]--
  155. local function checkYScroll()
  156.     if yscroll + currline + _yoff > h - 3 then
  157.         yscroll = h - 3 - currline - _yoff
  158.         lscroll = true
  159.     elseif yscroll + currline + _yoff < 3 then
  160.         yscroll = 3 - currline - _yoff
  161.         lscroll = true
  162.     end
  163. end
  164.  
  165. --[[ Lowers the current line by 1, if possible
  166.      Parameters: none
  167.      Returns: true if there is a next line; false otherwise
  168. ]]--
  169. function nextLine()
  170.     if currline < #doc[currpage] then
  171.         currline = currline + 1
  172.     elseif currpage < #doc then
  173.         currpage = currpage + 1
  174.         currline = 1
  175.     else return false end
  176.     return true
  177. end
  178.  
  179. --[[ Ensures the cursor is in a valid position. Being outside the size of
  180.      a string or before a forbidden character like a newline or tab are fixed.
  181.      Parameters: none
  182.      Returns: none
  183. ]]--
  184. local function fixCursorPos()
  185.     if not doc[currpage] then
  186.         currpage = #doc
  187.         currline = #doc[#doc]
  188.     end
  189.     if not doc[currpage][currline] then
  190.         if currline > linelen then
  191.             currline = linelen
  192.         else currline = #doc[currpage] end
  193.         currind = #doc[currpage][currline] + 1
  194.     end
  195.     if currind < 1 then currind = 1
  196.     elseif currind > #doc[currpage][currline] + 1 then
  197.         currind = #doc[currpage][currline] + 1
  198.     end
  199.     if currind > 1 and doc[currpage][currline]:sub(currind-1) == "\n" then
  200.         currind = currind - 1
  201.     end
  202. end
  203.  
  204. --[[ Goes to the previous line, if possible
  205.      Parameters: none
  206.      Returns: true if there is a previous line; false otherwise
  207. ]]--
  208. local function lastLine()
  209.     if currline > 1 then
  210.         currline = currline - 1
  211.     elseif currpage > 1 then
  212.         currpage = currpage - 1
  213.         currline = pagelen
  214.     else return false end
  215.     return true
  216. end
  217.  
  218. --[[ Gets the previous line in the document to the one provided
  219.      Parameters: _page: the page in question (default to currpage)
  220.                  _line: the line in question (default to currline)
  221.      Returns: string=The previous line, or nil if on the first line
  222. ]]--
  223. local function getPrevLine(_page, _line)
  224.     if not _page or not _line then _page = currpage _line = currline end
  225.     if _page == 1 and _line == 1 then return nil
  226.     elseif _line == 1 then _line = pagelen _page = _page - 1
  227.     else _line = _line - 1 end
  228.     return doc[_page][_line]
  229. end
  230.  
  231. --[[ Gets the next line in the document to the one provided
  232.      Parameters: _page: the page in question (default to currpage)
  233.                  _line: the line in question (default to currline)
  234.      Returns: string=The next line, or nil if on the last line
  235. ]]--
  236. local function getNextLine(_page, _line)
  237.     if not _page or not _line then _page = currpage _line = currline end
  238.     if _page == #doc and _line == #doc[#doc] then return nil
  239.     elseif _line == #doc[_page] then _line = 1 _page = _page + 1
  240.     else _line = _line + 1 end
  241.     return doc[_page][_line]
  242. end
  243.  
  244. --[[ A helper that returns whether or not the input line is the last in the
  245.      document (the last line has a few special rules associated with it)
  246.      Parameters: _page: the page in question (default to currpage)
  247.                  _line: the line in question (default to currline)
  248.      Returns: true if the last line in document; false otherwise
  249. ]]--
  250. local function isLastLine(_page, _line)
  251.     if not _page or not _line then _page = currpage _line = currline end
  252.     return _page == #doc and _line == #doc[#doc]
  253. end
  254.  
  255. --[[ Adds a sequence of text to the current line and index
  256.      Parameters: val:String = the character to be inserted
  257.      Returns: none
  258. ]]--
  259. local function addCharacter(_val)
  260.     local _cltext = doc[currpage][currline]
  261.     if currind > #_cltext then
  262.         _cltext = _cltext.._val
  263.     else
  264.         _cltext = _cltext:sub(1, currind-1).._val.._cltext:sub(currind)
  265.     end
  266.     doc[currpage][currline] = _cltext
  267.     currind = currind + #_val
  268.     paginateOverflow()
  269.     -- Space actions must also be underflowed; they may have broken a word.
  270.     if _val == " " then paginateUnderflow() end
  271.     checkYScroll()
  272.     sChange = true
  273. end
  274.  
  275. --[[ Removes a character from the current line and index
  276.      Parameters: none
  277.      Returns: none
  278. ]]--
  279. function deleteCharacter()
  280.     --We first move our cursor to the character we want to delete
  281.     if currind == 1 then
  282.         if currline > 1 then currline = currline - 1
  283.         elseif currpage > 1 then
  284.             currpage = currpage - 1
  285.             currline = pagelen
  286.         else return end
  287.         currind = #doc[currpage][currline]
  288.     else currind = currind - 1 end
  289.     --Then create 2 'snips'; before and after the character
  290.     local _cline = doc[currpage][currline]
  291.     local _fsnip,_asnip = "",""
  292.     if currind > 1 then _fsnip = _cline:sub(1,currind-1) end
  293.     if currind < #_cline then _asnip = _cline:sub(currind+1) end
  294.     --We patch our snips, remove dead lines and do a cleanup
  295.     doc[currpage][currline] = _fsnip.._asnip
  296.     if doc[currpage][currline] == "" then
  297.         _lline = getPrevLine()
  298.         if not _lline or _lline:sub(#_lline) ~= "\n" then
  299.             removeLine(currpage, currline)
  300.         end
  301.     end
  302.     if not doc[currpage] or not doc[currpage][currline] then
  303.         currpage = #doc
  304.         currline = #doc[currpage]
  305.         _cl = #doc[currpage][currline]
  306.         if doc[currpage][currline]:sub(_cl,_cl) ~= "\n" then
  307.             currind = _cl + 1
  308.         end
  309.     end
  310.     --assert(false, currpage..","..currline..","..currind)
  311.     paginateUnderflow()
  312.     fixCursorPos()
  313.     checkYScroll()
  314.     sChange = true
  315. end
  316.  
  317. --[[ Removes an entire line from the document, and shifts all previous
  318.      lines up
  319.      Parameters: pagenum:int = the page the line is on
  320.                  linenum:int = the line to remove
  321.      Returns: none
  322. ]]--
  323. function removeLine(_pagenum, _linenum)
  324.     _linenum = _linenum + 1
  325.     if _linenum > pagelen then _linenum = 1 _pagenum = _pagenum + 1 end
  326.     local _lp = ""
  327.     --To delete, we copy each line to replace the previous
  328.     while _pagenum <= #doc and _linenum <= #doc[_pagenum] do
  329.         local _cbuff = doc[_pagenum][_linenum]
  330.         if _linenum == 1 then doc[_pagenum-1][pagelen] = _cbuff
  331.         else doc[_pagenum][_linenum-1] = _cbuff end
  332.         if _linenum == pagelen then _linenum = 1 _pagenum = _pagenum + 1
  333.         else _linenum = _linenum + 1 end
  334.         _lp = _cbuff
  335.     end
  336.     --And black out the very last so we know nothing is there.
  337.     _linenum = _linenum - 1
  338.     if (_pagenum == 1 and _linenum == 1) or _lp:sub(#_lp) == "\n" then
  339.         doc[#doc][#doc[#doc]] = ""
  340.     else doc[#doc][#doc[#doc]] = nil end
  341.     if #doc[#doc] == 0 and #doc ~= 1 then
  342.         table.remove(doc, #doc)
  343.     end
  344.     --So long as we still have something in the doc
  345.     if #doc[1] == 0 then doc[1][1] = "" end
  346. end
  347.  
  348. --[[ Inserts a line of text into a specified index. The replaced line and
  349.      all subsequent lines are pushed down the document
  350.      Parameters: _pagenum:int = the page the line is one
  351.                  _linenum:int = the line in which to insert
  352.                  _text:string = the text to insert. Defaults to a newline.
  353. ]]--
  354. function insertLine(_pagenum, _linenum, _text)
  355.     if not _text then _text = "\n" end
  356.     local _pi,_li = #doc, #doc[#doc] + 1
  357.     if _li > linelen then _pi = _pi + 1 _li = 1 end
  358.     if not doc[_pi] then doc[_pi] = { } end
  359.     local _icount = 0
  360.     while _pi > _pagenum or _li > _linenum do
  361.         doc[_pi][_li] = getPrevLine(_pi,_li)
  362.         _li = _li - 1
  363.         if _li == 0 then _li = pagelen _pi = _pi - 1 end
  364.         _icount = _icount + 1
  365.     end
  366.     doc[_pagenum][_linenum] = _text
  367. end
  368.  
  369. --[[ Updates pagination at the point following the trailing line from the
  370.      cursor for underflow as a result of a delete edit action.
  371.      The pagination is optimized to only go so far as it has to when making
  372.      changes, but can cover the entire document through a switch.
  373.      Parameters: _cflag:bool= set to true to check entire document
  374.      Returns: none
  375. ]]--
  376. function paginateUnderflow(_cflag)
  377.     --Whether or not we need to keep paginating
  378.     local _pfollow = false
  379.     --Whether or not a vulnerable underflow occurred. These are characterized
  380.     --as underflows that leave a partial word behind.
  381.     local _vocc = false
  382.     --How many pagination checks we need to run. It's a minimum of 2
  383.     --if we're on the same line
  384.     local _pcount = 3
  385.     --The line we start at on this page
  386.     local _pi,_nli = currpage, currline
  387.     --Our comparataor line
  388.     local _cpline = doc[_pi][_nli]
  389.     --The last character of the second to last line
  390.     local _llsnip = nil
  391.     --Where do we start, here or next line?
  392.     if _pi == 1 and _nli == 1 then _nli = _nli + 1 _pcount = _pcount - 1
  393.     elseif _nli == 1 then _cpline = doc[_pi-1][pagelen]
  394.     else _cpline = doc[_pi][_nli-1] end
  395.  
  396.     while _pi <= #doc do
  397.         local _li = _nli
  398.         while _li <= #doc[_pi] do
  399.             _llsnip = _cpline:sub(#_cpline)
  400.             local _cline = doc[_pi][_li]
  401.             local _nsp = -1
  402.             local _conn = 0
  403.             if _cline:sub(#_cline) == "\n" then _conn = -1 end
  404.             --We search for a word that will fit on the previous line
  405.             for _nsi = linelen - #_cpline,1,-1 do
  406.                 if _cline:sub(_nsi,_nsi) == " " then
  407.                     _nsp = _nsi
  408.                     --Because we don't use it here
  409.                     _conn = 0
  410.                     break
  411.                 end
  412.             end
  413.             --Single words short enough can be moved too
  414.             if #_cline+_conn <= linelen - #_cpline then
  415.                 _nsp = #_cline+_conn
  416.                 --A little exception in the event we leave our cursor behind
  417.             end
  418.             --And if there's no space at the end of the line, it moves back too
  419.             if _nsp == -1 and _cpline:sub(#_cpline) ~= " "
  420.                     and _cpline:sub(#_cpline) ~= "\n" then
  421.                 _nsp = linelen - #_cpline
  422.                 --Also unused here
  423.                 _conn = 0
  424.                 --This however now requires an overflow check
  425.                 _vocc = true
  426.             end
  427.            
  428.             --If no such word exists, we're done
  429.             --Also, if our comparator line has newline, underflow is disallowed
  430.             --NOTE: Hideous code. This could be done far better.
  431.             if (_nsp == -1 or _cpline:sub(#_cpline) == "\n") then
  432.                 if _pi ~= currpage and _li ~= currline and _pcount == 0 then
  433.                     _pfollow = true
  434.                     break
  435.                 end
  436.             else
  437.                 --Otherwise we snip our words from the current line,
  438.                 local _snip = _cline:sub(1,_nsp-_conn)
  439.                 doc[_pi][_li] = _cline:sub(_nsp+1-_conn)
  440.                 --then stick the snip on the next line (lazy coding but meh)
  441.                 if _li > 1 then doc[_pi][_li-1] = doc[_pi][_li-1].._snip
  442.                 else
  443.                     doc[_pi-1][pagelen] = doc[_pi-1][pagelen].._snip
  444.                 end
  445.                 --If we performed a deletion on the cursor line line and the
  446.                 --cursor is within the clip, we must move it
  447.                 if _pi == currpage and _li == currline and currind <= _nsp-_conn then
  448.                     currind = currind + #_cpline
  449.                     if _li == 1 then
  450.                         currpage = currpage -1
  451.                         currline = pagelen
  452.                     else
  453.                         currline = currline - 1
  454.                     end
  455.                 end
  456.             end
  457.             --Remove any excess lines
  458.             local _slsnip = getPrevLine(#doc, #doc[#doc])
  459.             _slsnip = _slsnip:sub(#_slsnip)
  460.             if doc[_pi][_li] == "" and not(isLastLine(_pi, _li)
  461.                     and _slsnip == "\n") then
  462.                 removeLine(_pi,_li)
  463.                 _li = _li - 1
  464.             end
  465.             --Update our variables, and we're done here.
  466.             if not doc[_pi] or not doc[_pi][_li] then break end
  467.             _cpline = doc[_pi][_li]
  468.             _li = _li + 1
  469.             _pcount = math.max(_pcount-1, 0)
  470.         end
  471.         if _pfollow then break end
  472.         _nli = 1
  473.         _pi = _pi + 1
  474.     end
  475.     --Unsafe carryovers have to check for overflow pagination.
  476.     if _vocc then
  477.         paginateOverflow()
  478.     end
  479. end
  480.  
  481. --[[ Updates pagination at the point following the cursor for overflow as
  482.      a result of an insertion edit action.
  483.      Parameters: _cflag:bool= set to true to check entire document
  484.      Returns: none
  485. ]]--
  486. function paginateOverflow(_cflag)
  487.     --Whether or not we need to keep paginating
  488.     local _pfollow = false
  489.     --The line we start at on this page
  490.     local _pi,_nli = currpage, currline
  491.     --Whether or not we've done an insertion, and need to perform another check
  492.     local _icheck = false
  493.     while _pi <= #doc do
  494.         _li = _nli
  495.         while _li <= #doc[_pi] do
  496.             local _cltext = doc[_pi][_li]
  497.             --First we check to see if it's a carry-on line
  498.             local _conn = 0
  499.             local _nl = getNextLine(_pi,_li)
  500.             if _nl and _cltext:sub(#_cltext) ~= " " and
  501.                     _nl:sub(1,1) ~= " " then
  502.                 _conn = _nl:find(" ")
  503.                 if not _conn then _conn = _nl:find("\n") end
  504.                 if not _conn then _conn = #_nl
  505.                 else _conn = _conn - 1 end
  506.             end
  507.             if _cltext:sub(#_cltext) == "\n" then _conn = -1 end
  508.             --And a bit of leniency for newlines (they don't print anyway)
  509.             --If the line is shorter than the line length, we are done
  510.             if (#_cltext + _conn <= linelen and not _cflag and not _icheck)
  511.                     or #_cltext == 0 then
  512.                 _pfollow = true
  513.                 break
  514.             end
  515.             _icheck = false
  516.  
  517.             --Otherwise we find the nearest space to the edge of the document
  518.             local _nsp = -1
  519.             for _nsi = linelen, 1, -1 do
  520.                 if _cltext:sub(_nsi,_nsi) == " " then
  521.                     _nsp = _nsi
  522.                     break
  523.                 end
  524.             end
  525.             --The excess string is carried to the front of the next line or page
  526.             --Notice the space stays if there is more than one word on that line
  527.             if _nsp == -1 then _nsp = linelen end
  528.             doc[_pi][_li] = doc[_pi][_li]:sub(1,_nsp)
  529.             _carrystr = _cltext:sub(_nsp+1)
  530.             if _li == pagelen then
  531.                 if _pi == #doc then
  532.                     doc[_pi+1] = { _carrystr }
  533.                 --Newlines must be carried, and cannot overflow.
  534.                 elseif _carrystr:sub(#_carrystr) == "\n" then
  535.                     insertLine(_pi+1,1,_carrystr)
  536.                     _icheck = true
  537.                 else
  538.                     doc[_pi+1][1] = _carrystr..doc[_pi+1][1]
  539.                 end
  540.             elseif _li == #doc[_pi] then
  541.                 doc[_pi][_li+1] = _carrystr
  542.             elseif _carrystr:sub(#_carrystr) == "\n" then
  543.                 insertLine(_pi,_li+1,_carrystr)
  544.                 _icheck = true
  545.             else
  546.                 doc[_pi][_li+1] = _carrystr..doc[_pi][_li+1]
  547.             end
  548.             --If this pagination affected our cursor, we move down a line
  549.             if _pi == currpage and _li == currline and
  550.                      currind > #doc[currpage][currline] then
  551.                 currind = currind - #doc[currpage][currline]
  552.                 if currline == pagelen then
  553.                     currline = 1
  554.                     currpage = currpage + 1
  555.                 else
  556.                     currline = currline + 1
  557.                 end
  558.             end
  559.             _li = _li + 1
  560.         end
  561.         if _pfollow then break end
  562.         _nli = 1
  563.         _pi = _pi + 1
  564.     end
  565. end
  566.  
  567. --[[ Draws the 'whitespace' surrounding the page and the menu. This includes both
  568.      page-display features like the margins around the page and borders to make it more
  569.      distinct.
  570.      Parameters: none
  571.      Returns: none
  572. ]]--
  573. local function displayClearPage()
  574.     --Drawing the background
  575.     term.setBackgroundColour(colours.lightBlue)
  576.     term.clear()
  577.     term.setBackgroundColour(colours.grey)
  578.     --Adding a little border to the page to make it more distinct
  579.     local _ydpos = _yoff + yscroll - 1
  580.     if _ydpos > 0 and _ydpos <= h then
  581.         term.setCursorPos(_xoff + 1, _ydpos)
  582.         term.write(string.rep(" ", 2 + linelen))
  583.         term.setBackgroundColour(colours.white)
  584.         term.setCursorPos(_xoff, _ydpos + 1)
  585.         term.write(string.rep(" ", 2 + linelen))
  586.     end
  587.     if _ydpos + pagelen + 2 > 0 and _ydpos + pagelen + 2 <= h then
  588.         term.setBackgroundColour(colours.white)
  589.         term.setCursorPos(_xoff, _ydpos + pagelen + 2)
  590.         term.write(string.rep(" ", 2 + linelen))
  591.     end
  592.     for i = math.max(0, _ydpos), math.min(h, pagelen + yscroll + _yoff) do
  593.         if i > _ydpos or i == 0 then
  594.             term.setBackgroundColour(colours.white)
  595.             term.setCursorPos(_xoff, i)
  596.             term.write(" ")
  597.             term.setCursorPos(_xoff + linelen + 1, i)
  598.             term.write(" ")
  599.         else
  600.             term.setCursorPos(_xoff + linelen + 2, i)
  601.         end
  602.         term.setBackgroundColour(colours.grey)
  603.         term.write(" ")
  604.     end
  605. end
  606.  
  607.  
  608. --[[ A presently mostly empty function that displays tools and information about the
  609.      current document for the user
  610.      Parameters: none
  611.      Returns: none
  612. ]]--
  613. local function displayInterface()
  614.     term.setBackgroundColour(colours.lightBlue)
  615.     term.setTextColour(colours.black)
  616.     --A little interface feature to tell the user what page they're viewing
  617.     local _ppos = string.rep(" ",math.floor(math.log10(#doc)) - math.floor(math.log10(currpage)))
  618.             ..currpage.." of "..#doc
  619.     term.setCursorPos(w-#_ppos-1,h)
  620.     term.setTextColour(colours.black)
  621.     term.write(_ppos)
  622.     term.setCursorPos(1,1)
  623.     term.setBackgroundColour(colours.lightGrey)
  624.     if testMode then term.setBackgroundColour(colours.red) end
  625.     term.setTextColour(colours.black)
  626.     term.clearLine()
  627.     term.write(" File")
  628.     local _t = sPath or "Unnamed"
  629.     if #_t > w - 14 then _t = _t:sub(1,#_t-math.max(sName,14)-3).."..."..sName end
  630.     term.setTextColour(colours.grey)
  631.     term.setCursorPos(w/2-#_t/2,1)
  632.     term.write(_t)
  633.     if sChange then
  634.         term.setTextColour(colours.red)
  635.         term.write("*")
  636.     end
  637. end
  638.  
  639. --[[ Draws the currently selected page. This prints exactly the contents of the page and
  640.      whitespace to fill out the gaps as necessary.
  641.      Parameters: page:The page of the document to print
  642.                  _out: The output construct
  643.                  _x:int = the x offset for the page to be printed at
  644.                  _y:int = the y offset for the page to be printed at
  645.      Returns: none
  646. ]]--
  647. function printPage(page, _out, _x, _y)
  648.     if not _x or not _y then _x = 1 _y = 0 end 
  649.     --Displaying the page itself, both text and no text
  650.     for i=1,pagelen do
  651.         local _ydpos = i + _y
  652.         --This little optimization only works if _out == term, so it's not in for now.
  653.         --if _out == term and _ydpos > 1 and _ydpos <= h then
  654.         _out.setCursorPos(_x, i + _y)
  655.         if i > 0 and i <= #page then
  656.             local _disp = string.gsub(page[i], "\n"," ")
  657.             -- Our whitespace display function
  658.             if (whitespaceVis or testMode) and _out == term then
  659.                 for _c=1,#page[i] do
  660.                     local _chr = page[i]:sub(_c,_c)
  661.                     if _chr == " " then
  662.                         term.setTextColour(colours.red)
  663.                         term.write(".")
  664.                     elseif _chr == "\n" then
  665.                         term.setTextColour(colours.red)
  666.                         term.write(">")
  667.                     else
  668.                         term.setTextColour(colours.black)
  669.                         term.write(_chr)
  670.                     end
  671.                 end
  672.                 term.setTextColour(colours.black)
  673.             else
  674.                 --Bah. This has to change. Forget nice code. This program is one enormous, hideous hack.
  675.                 _out.write(_disp)
  676.             end
  677.             _out.write(string.rep(" ", math.max(linelen-#_disp, 0)))
  678.         else
  679.             _out.write(string.rep(" ", linelen))
  680.         end
  681. --      end
  682.     end
  683. end
  684.  
  685. --[[ Clears and redraws the screen ]]--
  686. local function clearAndRedraw()
  687.     displayClearPage()
  688.     term.setBackgroundColour(colours.white)
  689.     term.setTextColour(colours.black)
  690.     printPage(doc[currpage], term, _xoff + 1, _yoff + yscroll)
  691.     displayInterface()
  692. end
  693.  
  694. --[[ Draws the WYSIwrite logo. ENORMOUSLY lazy but I'm over this. ]]--
  695. local function drawLogo(x,y)
  696.     term.setBackgroundColour(colours.grey)
  697.     for i=2,3 do
  698.         term.setCursorPos(x,y+i)
  699.         term.write(string.rep(" ", 5))
  700.     end
  701.     term.setBackgroundColour(colors.white)
  702.     for i=0,3 do if i ~= 2 then
  703.         term.setCursorPos(x + 1, y+i)
  704.         term.write(string.rep(" ",3))
  705.     end end
  706.     term.setCursorPos(x+2,y+1)
  707.     term.write("W")
  708. end
  709.  
  710. --[[ Shows a quick "about" page for those curious ]]--
  711. local function displayAbout()
  712.     local _dw,_dh = w/2+3,6
  713.     local _l,_t = math.floor(w/2-_dw/2),6
  714.    
  715.     clearAndRedraw()
  716.     term.setBackgroundColour(colours.lightGrey)
  717.     term.setTextColour(colours.black)
  718.     for _y=0,_dh do
  719.         term.setCursorPos(_l,_t+_y)
  720.         term.write(string.rep(" ", _dw))
  721.     end
  722.     term.setCursorPos(_l + 7, _t + 2)
  723.     term.write("WYSIwrite")
  724.     term.setCursorPos(_l + 7, _t + 4)
  725.     term.write("By Nitrogen Fingers")
  726.     drawLogo(_l + 1, _t + 1)
  727.     os.pullEvent()
  728.     lscroll = true
  729. end
  730.  
  731. --[[ Gets the previous file in the path, if there is one ]]--
  732. local function getPreviousDir(_path)
  733.     if _path == "" or _path == "/" then return path
  734.     else
  735.         _path = _path:reverse()
  736.         return _path:sub(_path:find("/.*")+1):reverse()
  737.     end
  738. end
  739.  
  740.         --[[ Interface malarky ]]--
  741.  
  742.  
  743. --[[ A recycled centre print function I've gotten a LOT of mileage out of
  744.      Parameters: msg:string= the message to display
  745.                  height:int= the y position of the start of the message
  746.                  width:int= the allowed width of the message
  747.                  offset:int= the leftmost border of the message
  748.      Returns:int= the number of lines printed
  749. ]]--
  750. local function wprintOffCenter(msg, height, width, offset)
  751.     local inc = 0
  752.     local ops = 1
  753.     while #msg - ops > width do
  754.         local nextspace = 0
  755.         while string.find(msg, " ", ops + nextspace) and
  756.                 string.find(msg, " ", ops + nextspace) - ops < width do
  757.             nextspace = string.find(msg, " ", nextspace + ops) + 1 - ops
  758.         end
  759.         local ox,oy = term.getCursorPos()
  760.         term.setCursorPos(width/2 - (nextspace)/2 + offset, height + inc)
  761.         inc = inc + 1
  762.         term.write(string.sub(msg, ops, nextspace + ops - 1))
  763.         ops = ops + nextspace
  764.     end
  765.     term.setCursorPos(width/2 - #string.sub(msg, ops)/2 + offset, height + inc)
  766.     term.write(string.sub(msg, ops))
  767.    
  768.     return inc + 1
  769. end
  770.  
  771. --[[Produces a nice dropdown menu based on a table of strings. Depending on the position, this will auto-adjust the position
  772.         of the menu drawn, and allows nesting of menus and sub menus. Clicking anywhere outside the menu will cancel and return nothing
  773.         Params: x:int = the x position the menu should be displayed at
  774.                 y:int = the y position the menu should be displayed at
  775.                 options:table = the list of options available to the user, as strings or submenus (tables of strings, with a name parameter)
  776.         Returns:string the selected menu option.
  777. ]]--
  778. local function displayDropDown(x, y, options, noTitle)
  779.     inDropDown = true
  780.     if noTitle then y = y - 1 end
  781.     --Figures out the dimensions of our thing
  782.     local longestX = #options.name
  783.     for i=1,#options do
  784.         local currVal = options[i]
  785.         if type(currVal) == "table" then currVal = currVal.name end
  786.         longestX = math.max(longestX, #currVal)
  787.     end
  788.     local xOffset = math.max(0, longestX - ((w-2) - x) + 1)
  789.     local yOffset = math.max(0, #options - ((h-1) - y))
  790.    
  791.     local clickTimes = 0
  792.     local tid = nil
  793.     local selection = nil
  794.     while clickTimes < 4 do
  795.         if not noTitle then
  796.             term.setCursorPos(x-xOffset,y-yOffset)
  797.             term.setBackgroundColour(colours.blue)
  798.             term.setTextColour(colours.white)
  799.             term.write(options.name..string.rep(" ", longestX-#options.name + 1))
  800.         end
  801.  
  802.         for i=1,#options do
  803.             term.setCursorPos(x-xOffset, y-yOffset+i)
  804.             local currVal = options[i]
  805.             if type(currVal.value) == "table" then
  806.                 currVal.enabled = #currVal.value > 0
  807.             end
  808.            
  809.             if i==selection and clickTimes % 2 == 0 then
  810.                 term.setBackgroundColour(colours.blue)
  811.                 if options[selection].enabled then term.setTextColour(colours.white)
  812.                 else term.setTextColour(colours.grey) end
  813.             else
  814.                 local _tcol = colours.black
  815.                 if not currVal.enabled then _tcol = colours.grey end
  816.                 term.setBackgroundColour(colours.lightGrey)
  817.                 term.setTextColour(_tcol)
  818.             end
  819.             if type(currVal.value) == "table" then
  820.                 term.write(currVal.name..string.rep(" ", longestX-#currVal.name))
  821.                 term.setBackgroundColour(colours.blue)
  822.                 term.setTextColour(colours.white)
  823.                 term.write(">")
  824.             else
  825.                 term.write(currVal.name..string.rep(" ", longestX-#currVal.name + 1))
  826.             end
  827.         end
  828.        
  829.         local id, p1, p2, p3 = os.pullEvent()
  830.         if id == "timer" then
  831.             if p1 == tid then
  832.                 clickTimes = clickTimes + 1
  833.                 if clickTimes > 2 then
  834.                     break
  835.                 else
  836.                     tid = os.startTimer(0.1)
  837.                 end
  838.             end
  839.         elseif id == "key" then
  840.             if p1 == keys.leftCtrl then
  841.                 selection = ""
  842.                 break
  843.             elseif selection == nil or (p1 == keys.down and selection < #options) then
  844.                 selection = selection or 0
  845.                 _os = selection
  846.                 repeat selection = selection + 1
  847.                 until selection == #options + 1 or options[selection].enabled
  848.                 --if selection == #options + 1 then selection = _os end
  849.             elseif p1 == keys.up and selection > 1 then
  850.                 _os = selection
  851.                 repeat selection = selection - 1
  852.                 until selection == 0 or options[selection].enabled
  853.                 if selection == 0 then selection = _os end
  854.             elseif p1 == keys.enter and options[selection].enabled then
  855.                 tid = os.startTimer(0.1)
  856.                 clickTimes = clickTimes - 1
  857.             end
  858.         elseif id == "mouse_click" then
  859.             local _xp, _yp = x - xOffset, y - yOffset
  860.             if p2 >= _xp and p2 <= _xp + longestX + 1 and
  861.             p3 >= _yp+1 and p3 <= _yp+#options then
  862.                 if options[p3 - _yp].enabled then
  863.                     selection = p3-(_yp)
  864.                     tid = os.startTimer(0.1)
  865.                 end
  866.             else
  867.                 selection = ""
  868.                 break
  869.             end
  870.         end
  871.     end
  872.    
  873.     local _val = selection
  874.     if type(selection) == "number" then
  875.         selection = options[selection].value
  876.     end
  877.    
  878.     if type(selection) == "string" then
  879.         inDropDown = false
  880.         return selection
  881.     elseif type(selection) == "table" then
  882.         return displayDropDown(x + longestX + 1, y + _val, selection, true)
  883.     elseif type(selection) == "function" then
  884.         local _rval = selection()
  885.         if not _rval then return "" else return _rval end
  886.     end
  887. end
  888.  
  889.  
  890. --[[ Copied out of other menus, simply provides a display to indicate something has happened.
  891.      Parameters: ctitle:string = the title of the dialogue
  892.                  msg:string = the message in the dialogue
  893.                  arg:{string, colour} = pairs of buttons with strings and their text colour (background is default)
  894.      Returns: none
  895. ]]--
  896. local function displayConfirmDialogue(ctitle, msg, ...)
  897.     local _doffX, _doffY = 8, 5
  898.     --We actually print twice- once to get the lines, second time to print proper. Easier this way.
  899.     local lines = wprintOffCenter(msg, _doffY, w - (_doffX+2) * 2, _doffX + 2)
  900.    
  901.     term.setCursorPos(_doffX, 3)
  902.     term.setBackgroundColour(colours.blue)
  903.     term.setTextColour(colours.white)
  904.     term.write(string.rep(" ", w - _doffX * 2))
  905.     term.setCursorPos(_doffX + (w - _doffX * 2)/2 - #ctitle/2, 3)
  906.     term.write(ctitle)
  907.     term.setTextColour(colours.black)
  908.     term.setBackgroundColour(colours.lightGrey)
  909.     term.setCursorPos(_doffX, 4)
  910.     term.write(string.rep(" ", w - _doffX * 2))
  911.     for i = _doffY, _doffY + lines do
  912.         term.setCursorPos(_doffX, i)
  913.         term.write(" "..string.rep(" ", w - (_doffX) * 2 - 2).." ")
  914.     end
  915.     wprintOffCenter(msg, _doffY, w - (_doffX+2) * 2, _doffX + 2)
  916.    
  917.     if arg then
  918.         term.setCursorPos(_doffX, _doffY + lines + 1)
  919.         term.write(string.rep(" ", w - _doffX * 2))
  920.         term.setCursorPos(_doffX, _doffY + lines + 2)
  921.         term.write(string.rep(" ", w - _doffX * 2))
  922.    
  923.         local _sspace = 0
  924.         for _k,_v in ipairs(arg) do _sspace = _sspace + #_v[1] end
  925.         _sspace = (w - (_doffX * 2) - _sspace - (2 * #arg))/(#arg)
  926.         if _sspace <= #arg - 1 then assert(false, "Too little space: needed "..(#arg - 1)..", got ".._sspace) end
  927.         term.setBackgroundColour(colours.grey)
  928.         term.setCursorPos(_doffX + 1, _doffY + lines + 1)
  929.         local _spart = false
  930.         for i=1,#arg do
  931.             _v = arg[i]
  932.             arg[i][3] = term.getCursorPos()
  933.             term.setTextColour(_v[2])
  934.             term.write(" ".._v[1].." ")
  935.             local _vspace = math.floor(_sspace)
  936.             if i >= #arg/2 and not _spart then _vspace = math.ceil(_sspace) _spart = true end
  937.             local _x,_y = term.getCursorPos()
  938.             term.setCursorPos(_x + _vspace, _y)
  939.         end
  940.     end
  941.    
  942.     --In the event of a message, the player hits anything to continue
  943.     while true do
  944.         local _id,_p1,_p2,_p3 = os.pullEvent()
  945.         if (not arg or #arg == 0) and (id == "key" or id == "mouse_click" or id == "mouse_drag") then
  946.             break
  947.         elseif _id == "key" then
  948.             if _p1 == keys.enter then return 1 end
  949.         elseif _id == "mouse_click" and _p3 == _doffY + lines + 1 then
  950.             for i=1,#arg do
  951.                 _v = arg[i]
  952.                 if _p2 >= _v[3] and _p2 <= _v[3] + #_v[1] + 1 then return i end
  953.             end
  954.         end
  955.     end
  956. end
  957.  
  958. --[[ Runs a printing interface that lets users select the pages and number of copies to print
  959.      Also displays toner and paper levels, and returns the start, end and copy number, or null.
  960. ]]--
  961. local function displayPrint(_printer,_w,_h,_y,_x)
  962.     _w = math.floor(_w)
  963.     if not _x then _x = math.floor(w/2-_w/2) else _x = math.floor(_x) end
  964.     if not _y then _y = math.floor(h/2-_h/2) end
  965.     local _bc,_tc = colours.lightGrey, colours.black
  966.     local _btc,_ttc = colours.blue, colours.white
  967.     local _bfc,_tfc,_tdc = colours.black, colours.white, colours.green
  968.     --The position of the text boxes (which can vary)
  969.     local _cXoff,_cYoff = 0,0
  970.     local _pX,_pY,_cX,_cY = 0,0,0,0
  971.     --The selected pages and copies
  972.     local _pstart, _pend, _copies = 1, #doc, 1
  973.     --The amount of paper and ink required
  974.     local _pageCount = (_pend - _pstart + 1) * _copies
  975.     local _ilX,_ilY = 0,0
  976.    
  977.     local _wsel = 0
  978.    
  979.     local function _drawWindow()
  980.         --Window title
  981.         term.setBackgroundColour(_btc)
  982.         term.setTextColour(_ttc)
  983.         term.setCursorPos(_x,_y)
  984.         _title = "Print"
  985.         term.write(string.rep(" ", math.floor(_w/2) - math.floor(#_title/2))
  986.                 .._title..string.rep(" ", math.ceil(_w/2) - math.ceil(#_title/2)-1))
  987.         term.setBackgroundColour(colours.red)
  988.         term.setTextColour(colours.white)
  989.         term.write("x")
  990.         --Body and main options
  991.         term.setBackgroundColour(_bc)
  992.         term.setTextColour(_tc)
  993.         for i=1,_h do
  994.             term.setCursorPos(_x,_y+i)
  995.             term.write(string.rep(" ", _w))
  996.         end
  997.        
  998.         --Hardcoded value. To change.
  999.         local _plen = math.log10(#doc) + 1
  1000.         term.setCursorPos(_x + 1, _y + 2)
  1001.         term.write ("Print: ")
  1002.         _pX,_pY = term.getCursorPos()
  1003.         term.setBackgroundColour(colours.white)
  1004.         term.write(string.rep(" ", _plen))
  1005.         term.setBackgroundColour(_bc)
  1006.         term.write("-")
  1007.         term.setBackgroundColour(colours.white)
  1008.         term.write(string.rep(" ", _plen))
  1009.         term.setBackgroundColour(_bc)
  1010.        
  1011.         local _ctoken = "Copies: "
  1012.         local _clen,_cx,_cy = 2, term.getCursorPos()
  1013.         if _cx + #_ctoken + _clen + 2 > _x + _w - 1 then
  1014.             _cXoff = 1
  1015.             _cYoff = 2
  1016.             _ilY = _cy + 4
  1017.             term.setCursorPos(_x + _cXoff, _cy + _cYoff)
  1018.         else
  1019.             _cXoff = _cx + #_ctoken + 3
  1020.             _ilY = _cy + 2
  1021.             term.write("  ")
  1022.         end
  1023.         term.write(_ctoken)
  1024.         _cX, _cY = term.getCursorPos()
  1025.         term.setBackgroundColour(colours.white)
  1026.         term.write("  ")
  1027.        
  1028.         term.setBackgroundColour(_bc)
  1029.         term.setCursorPos(_x + 1, _ilY)
  1030.         term.write("Toner level: ")
  1031.         term.setCursorPos(_x + 1, _ilY + 1)
  1032.         term.write("Paper level: ")
  1033.         _ilX = term.getCursorPos()
  1034.        
  1035.         --Cancel button
  1036.         term.setBackgroundColour(colours.grey)
  1037.         term.setTextColour(colours.red)
  1038.         term.setCursorPos(_x + _w - 10, _y + _h - 1)
  1039.         term.write(" Cancel ")
  1040.     end
  1041.    
  1042.     local function _drawPrintOptions()
  1043.         --Numbers in the pages and copies box
  1044.         if _wsel ~= 1 and _pstart == nil then _pstart = 1 end
  1045.         if _wsel ~= 2 and _pend == nil then _pend = 1 end
  1046.         if _wsel ~= 3 and _copies == nil then _copies = 1 end
  1047.        
  1048.         term.setCursorPos(_pX, _pY)
  1049.         term.setTextColour(colours.black)
  1050.         if _wsel == 1 then term.setBackgroundColour(colours.orange)
  1051.         else term.setBackgroundColour(colours.white) end
  1052.         if _pstart == nil then term.write(string.rep(" ", math.log10(#doc) + 1))
  1053.         else term.write(string.rep(" ", math.floor(math.log10(#doc)) - math.floor(math.log10(_pstart))).._pstart) end
  1054.         if _wsel == 2 then term.setBackgroundColour(colours.orange)
  1055.         else term.setBackgroundColour(colours.white) end
  1056.         term.setCursorPos(term.getCursorPos() + 1, _pY)
  1057.         if _pend == nil then term.write(string.rep(" ", math.log10(#doc) + 1))
  1058.         else term.write(string.rep(" ", math.floor(math.log10(#doc)) - math.floor(math.log10(_pend))).._pend) end
  1059.         if _wsel == 3 then term.setBackgroundColour(colours.orange)
  1060.         else term.setBackgroundColour(colours.white) end
  1061.         term.setCursorPos(_cX, _cY)
  1062.         if _copies == nil then term.write(string.rep(" ", 2))
  1063.         else term.write(string.rep(" ", 1 - math.floor(math.log10(_copies))).._copies) end
  1064.        
  1065.         --Ink and paper levels
  1066.         local _iLvl, _pLvl = _printer.getInkLevel(), _printer.getPaperLevel()
  1067.         if not _pstart or not _pend or not _copies then _pageCount = -1
  1068.         else _pageCount = (_pend - _pstart + 1) * _copies end
  1069.         if _pageCount <= 0 then _pageCount = -1 end
  1070.         term.setBackgroundColour(_bc)
  1071.         if _iLvl < _pageCount then term.setTextColour(colours.red)
  1072.         else term.setTextColour(colours.black) end
  1073.         term.setCursorPos(_ilX, _ilY)
  1074.         term.write(_iLvl.."")
  1075.         term.setTextColour(colours.black)
  1076.         if _pageCount == -1 then term.write(" (--)")
  1077.         else term.write(" (".._pageCount..")") end
  1078.         term.write(string.rep(" ", _x + _w - term.getCursorPos()))
  1079.         term.setCursorPos(_ilX, _ilY + 1)
  1080.         if _pLvl < _pageCount then term.setTextColour(colours.red)
  1081.         else term.setTextColour(colours.black) end
  1082.         term.write(_pLvl.."")
  1083.        
  1084.         --Print/Invalid button
  1085.         term.setCursorPos(_x + 1, _y + _h - 1)
  1086.         term.setBackgroundColour(colours.grey)
  1087.         if not _pstart or not _pend or _pstart > _pend then
  1088.             term.setTextColour(colours.lightGrey)
  1089.             term.write(" Invalid ")
  1090.         else
  1091.             term.setTextColour(colours.green)
  1092.             term.write(" Print ")
  1093.             term.setBackgroundColour(colours.lightGrey)
  1094.             term.write("  ")
  1095.         end
  1096.     end
  1097.    
  1098.     local function _runInput()
  1099.         while true do
  1100.             local _id, _p1, _p2, _p3 = os.pullEvent()
  1101.             if _id == "mouse_click" then
  1102.                 if _p2 >= _pX and _p2 <= _pX + math.floor(math.log10(#doc)) and _p3 == _pY then
  1103.                     _wsel = 1
  1104.                     _pstart = nil
  1105.                     _drawPrintOptions()
  1106.                 elseif _p2 >= _pX + math.floor(math.log10(#doc)) + 2 and _p2 <= _pX + (2*math.floor(math.log10(#doc))) + 2 and
  1107.                         _p3 == _pY then
  1108.                     _pend = nil
  1109.                     _wsel = 2
  1110.                     _drawPrintOptions()
  1111.                 elseif _p2 >= _cX and _p2 <= _cX + 1 and _p3 == _cY then
  1112.                     _copies = nil
  1113.                     _wsel = 3
  1114.                     _drawPrintOptions()
  1115.                 elseif _p2 >= _x + 1 and _p2 <= _x + 8 and _p3 == _y + _h - 1 and _pstart <= _pend then
  1116.                     if _pstart == nil then _pstart = 1 end
  1117.                     if _pend == nil then _pend = #doc end
  1118.                     if _copies == nil then _copies = 1 end
  1119.                     return true
  1120.                 elseif (_p2 >= _x + _w - 9 and _p2 <= _x + _w - 1 and _p3 == _y + _h - 1) or
  1121.                         (_p2 == _x + _w - 1 and _p3 == _y) then
  1122.                     return false
  1123.                 else
  1124.                     _wsel = 0
  1125.                     _drawPrintOptions()
  1126.                 end
  1127.             elseif _id == "key" then
  1128.                 if _p1 == keys.delete or _p1 == keys.backspace then
  1129.                     if _wsel == 1 and _pstart then _pstart = math.floor(_pstart / 10) if _pstart == 0 then _pstart = nil end
  1130.                     elseif _wsel == 2 and _pend then _pend = math.floor(_pend / 10) if _pend == 0 then _pend = nil end
  1131.                     elseif _wsel == 3 and _copies then _copies = math.floor(_copies / 10) if _copies == 0 then _copies = nil end
  1132.                     end
  1133.                     _drawPrintOptions()
  1134.                 elseif _p1 == keys.tab then
  1135.                     _wsel = (_wsel % 3) + 1
  1136.                     _drawPrintOptions()
  1137.                 elseif _p1 == keys.enter and _pstart <= _pend then
  1138.                     if _pstart == nil then _pstart = 1 end
  1139.                     if _pend == nil then _pend = #doc end
  1140.                     if _copies == nil then _copies = 1 end
  1141.                     return true
  1142.                 end
  1143.             elseif _id == "char" and _wsel > 0 and tonumber(_p1) then
  1144.                 if _wsel == 1 then
  1145.                     if not _pstart then _pstart = 0 end
  1146.                     _pstart = (10 * _pstart) + tonumber(_p1)
  1147.                     if _pstart > #doc then _pstart = #doc
  1148.                     elseif _pstart == 0 then _pstart = nil end
  1149.                     _drawPrintOptions()
  1150.                 elseif _wsel == 2 then
  1151.                     if not _pend then _pend = 0 end
  1152.                     _pend = (10 * _pend) + tonumber(_p1)
  1153.                     if _pend > #doc then _pend = #doc
  1154.                     elseif _pend == 0 then _pend = nil end
  1155.                     _drawPrintOptions()
  1156.                 elseif _wsel == 3 then
  1157.                     if not _copies then _copies = 0 end
  1158.                     _copies = (10 * _copies) + tonumber(_p1)
  1159.                     if _copies > 99 then _copies = 99
  1160.                     elseif _copies == 0 then _copies = nil end
  1161.                     _drawPrintOptions()
  1162.                 end
  1163.             end
  1164.         end
  1165.     end
  1166.    
  1167.     _drawWindow()
  1168.     _drawPrintOptions()
  1169.     if _runInput() then return _pstart, _pend, _copies end
  1170. end
  1171.  
  1172. --[[ A more elaborate save menu than my first effort. Directory navigation and
  1173.      selection. It works well enough.
  1174. ]]--
  1175. local function displayFileBrowser(_flag,_h,_w,_y,_x)
  1176.     local _mt = { ["-s"] = "Save As", ["-b"] = "Browse Files", ["-l"] = "Load File" }
  1177.     if not _h then _h = math.floor(h/2) else _h = math.floor(_h) end
  1178.     if not _w then _w = math.floor(w/2) else _w = math.floor(_w) end
  1179.     if not _x then _x = math.floor(w/2-_w/2) + 1 else _x = math.floor(_x) end
  1180.     if not _y then _y = math.floor(h/2-_h/2) end
  1181.     local _bc,_tc = colours.lightGrey, colours.black
  1182.     local _btc,_ttc = colours.blue, colours.white
  1183.     local _bfc,_tfc,_tdc = colours.black, colours.white, colours.green
  1184.     --This is a nasty timesaver.
  1185.     local _cpath = shell.resolve(sPath or ".")
  1186.     if not _cpath:find("/") then _cpath = ""
  1187.     elseif sPath then _cpath = "/"..getPreviousDir(_cpath) end
  1188.     local _rlist = fs.list(_cpath)
  1189.     if _cpath ~= "/" and _cpath ~= "" then table.insert(_rlist, 1, "..") end
  1190.     local _scr = 0
  1191.     local _fname = ""
  1192.     local _abmsg = { ["-l"] = " Open ", ["-s"] = " Save As ",
  1193.             [1] = " Invalid ", [2] = " Overwrite " }
  1194.     local _labmsg = ""
  1195.     local _winh = _h - 7
  1196.     local _cpos = 0
  1197.    
  1198.     clearAndRedraw()
  1199.     --Some dedicated internal draw functions (to speed things up)
  1200.     local function _drawWindow()
  1201.         --Permanent parts of the window
  1202.         term.setBackgroundColour(_btc)
  1203.         term.setTextColour(_ttc)
  1204.         term.setCursorPos(_x,_y)
  1205.         term.write(string.rep(" ", math.floor(_w/2) - math.floor(#_mt[_flag]/2))
  1206.                 .._mt[_flag]..string.rep(" ", math.ceil(_w/2) -
  1207.                 math.ceil(#_mt[_flag]/2)-1))
  1208.         term.setBackgroundColour(colours.red)
  1209.         term.setTextColour(colours.white)
  1210.         term.write("x")
  1211.         term.setBackgroundColour(_bc)
  1212.         term.setTextColour(_tc)
  1213.         for i=1,_h do
  1214.             term.setCursorPos(_x,_y+i)
  1215.             term.write(string.rep(" ", _w))
  1216.         end
  1217.        
  1218.         term.setBackgroundColour(colours.grey)
  1219.         term.setTextColour(colours.red)
  1220.         term.setCursorPos(_x + _w - 10, _y + _h - 1)
  1221.         term.write(" Cancel ")
  1222.     end
  1223.    
  1224.     local function _drawBrowser()
  1225.         term.setBackgroundColour(_bc)
  1226.         term.setTextColour(_tc)
  1227.         local _dpath = _cpath
  1228.         if #_dpath > _w-4 then
  1229.             while _dpath:find("/") do
  1230.                 local _ind = _dpath:find("/") + 1
  1231.                 _dpath = _dpath:sub(_ind)
  1232.                 if #_dpath < _w-7 then
  1233.                     _dpath = "...".._dpath
  1234.                     break
  1235.                 end
  1236.             end
  1237.         end
  1238.         if #_dpath > _w-4 then _dpath = "...".._dpath:sub(_w-4-#_dpath) end
  1239.         term.setCursorPos(_x+2,_y+2)
  1240.         term.write(_dpath..string.rep(" ", _w-4-#_dpath))
  1241.        
  1242.         term.setBackgroundColour(_bfc)
  1243.         for i = 1 + _scr, _winh + _scr do
  1244.             _pth = _rlist[i] or ""
  1245.             term.setCursorPos(_x + 2, _y + 2 + i - _scr)
  1246.             if fs.isDir(_cpath.."/".._pth) then
  1247.                 term.setTextColour(_tdc)
  1248.             else term.setTextColour(_tfc) end
  1249.             term.write(" ".._pth..string.rep(" ", _w - 5 - #_pth))
  1250.         end
  1251.     end
  1252.    
  1253.     local function _drawActivationButton()
  1254.         _owrite = 0
  1255.         local _val = _cpath.."/".._fname
  1256.         if (not fs.exists(_val) and _flag == "-l") or
  1257.                 (fs.exists(_val) and fs.isDir(_val)) then
  1258.             _owrite = 1
  1259.         elseif fs.exists(_val) and _flag == "-s" then _owrite = 2 end
  1260.         term.setBackgroundColour(colours.grey)
  1261.         term.setCursorPos(_x + 2, _y + _h - 1)
  1262.         if _owrite == 1 and _flag ~= "-b" then
  1263.             term.setTextColour(colours.lightGrey)
  1264.             _labmsg = " Invalid "
  1265.         elseif _owrite == 2 then
  1266.             term.setTextColour(colours.orange)
  1267.             _labmsg = " Overwrite "
  1268.         elseif _flag == "-s" then
  1269.             term.setTextColour(colours.green)
  1270.             _labmsg = " Save "
  1271.         elseif _flag == "-l" then
  1272.             term.setTextColour(colours.green)
  1273.             _labmsg = " Open "
  1274.         end
  1275.         term.write(_labmsg)
  1276.         term.setBackgroundColour(_bc)
  1277.         term.write(string.rep(" ", #" Overwrite " - #_labmsg))
  1278.     end
  1279.    
  1280.     local function _drawWriteBar()
  1281.         term.setCursorPos(_x + 2, _y + _h - 3)
  1282.         term.setTextColour(colours.black)
  1283.         term.setBackgroundColour(colours.lightGrey)
  1284.         local _pm = "Save As "
  1285.         if _flag == "-l" then _pm = "Open " end
  1286.         term.write(_pm)
  1287.        
  1288.         local _msg = _fname
  1289.         if #_msg > _w - 8 - #_pm then _msg = _msg:sub(#_msg - (_w - 8 - #_pm)) end
  1290.         term.setBackgroundColour(colours.white)
  1291.         term.write(" ".._msg)
  1292.         _cpos = term.getCursorPos()
  1293.         term.write(string.rep(" ", math.max(_w - 5 - #_pm - #_msg, 1)))
  1294.     end
  1295.    
  1296.     _drawWindow()
  1297.     _drawActivationButton()
  1298.     _drawBrowser()
  1299.     _drawWriteBar()
  1300.    
  1301.     while true do
  1302.         term.setTextColour(colours.black)
  1303.         term.setCursorPos(_cpos, _y + _h - 3)
  1304.         term.setCursorBlink(true)
  1305.         local _id,_p1,_p2,_p3 = os.pullEvent()
  1306.        
  1307.         if _id == "key" then
  1308.             if _p1 == keys.backspace and #_fname > 0 then
  1309.                 _fname = _fname:sub(1,#_fname-1)
  1310.                 _drawActivationButton()
  1311.                 _drawWriteBar()
  1312.             elseif _p1 == keys.up and _scr > 0 then
  1313.                 _scr = _scr - 1
  1314.                 _drawBrowser()
  1315.             elseif _p1 == keys.down and _scr < #_rlist - _winh then
  1316.                 _scr = _scr + 1
  1317.                 _drawBrowser()
  1318.             elseif _p1 == keys.enter then
  1319.                 local _val = _cpath.."/".._fname
  1320.                 if (_flag == "-l" and fs.exists(_val) and not fs.isDir(_val))
  1321.                         or(_flag == "-s" and not fs.isDir(_val)) then
  1322.                     break
  1323.                 end
  1324.             elseif _p1 == keys.leftCtrl or _p == keys.rightCtrl then
  1325.                 _fname = nil
  1326.                 break
  1327.             end
  1328.         elseif _id == "char" then
  1329.             _fname = _fname.._p1
  1330.             _drawActivationButton()
  1331.             _drawWriteBar()
  1332.         elseif _id == "mouse_click" then
  1333.             if _p2 == _x + _w - 1 and _p3 == _y then
  1334.                 _fname = nil
  1335.                 break
  1336.             elseif _p2 >= _x + 2 and _p2 <= _x + _w - 2 and _p3 >= _y + 3 and
  1337.                     _p3 < _y + 3 + _winh then
  1338.                 _p3 = _p3 - _y - 2
  1339.                 local _val = _rlist[_p3 + _scr]
  1340.                 if _val == ".." then
  1341.                     --Wow why is there no reverse find
  1342.                     _cpath = getPreviousDir(_cpath)
  1343.                     _rlist = fs.list(_cpath)
  1344.                     if _cpath ~= "/" and _cpath ~= "" then table.insert(_rlist, 1, "..") end
  1345.                     _scr = 0
  1346.                     _drawBrowser()
  1347.                     _drawActivationButton()
  1348.                 elseif fs.isDir(_cpath.."/".._val) then
  1349.                     _cpath = _cpath.."/".._val
  1350.                     _rlist = fs.list(_cpath)
  1351.                     if _cpath ~= "/" and  _cpath ~= "" then table.insert(_rlist, 1, "..") end
  1352.                     _scr = 0
  1353.                     _drawBrowser()
  1354.                     _drawActivationButton()
  1355.                 else
  1356.                     _fname = _val or _fname
  1357.                     _drawActivationButton()
  1358.                     _drawWriteBar()
  1359.                 end
  1360.             elseif _p3 == _y + _h - 1 and _p2 >= _x + 2 and _p2 < _x + 2 + #        _labmsg and _labmsg ~= _abmsg[1] then
  1361.                 break
  1362.             elseif _p3 == _y + _h - 1 and _p2 >= _x + _w - 2 - #" Cancel " and
  1363.                     _p2 < _x + _w - 2 then
  1364.                 _fname = nil
  1365.                 return false
  1366.             end
  1367.         elseif _id == "mouse_scroll" then
  1368.             _scr = math.min(_scr, #_rlist - _winh - 1)
  1369.             _scr = math.max(_scr + _p1, 0)
  1370.             _drawBrowser()
  1371.         end
  1372.     end
  1373.     if _fname then return (_cpath.."/".._fname):sub(2),_fname else return nil end
  1374. end
  1375.  
  1376. local function menuSaveAs()
  1377.     local _sp,sn = displayFileBrowser("-s", 14, 24, 3)
  1378.     if _sp then
  1379.         sPath = _sp
  1380.         sName = _sn
  1381.         saveDocument()
  1382.     end
  1383. end
  1384.  
  1385. local function checkSave()
  1386.     if not sChange then return true end
  1387.     clearAndRedraw()
  1388. local _val = displayConfirmDialogue("Unsaved Changes", "Your document contains unsaved changes. Are you sure you want to    quit?", { [1] = "Save"; [2] = colours.green }, { [1] = "Discard"; [2] = colors.orange; }, { [1] = "Cancel";
  1389.     [2] = colors.red; })
  1390.     if _val == 1 then
  1391.         if sPath then saveDocument()
  1392.         else menuSaveAs() end
  1393.         return true
  1394.     elseif _val == 2 then
  1395.         return true
  1396.     else
  1397.         return false
  1398.     end
  1399. end
  1400.  
  1401. --[[
  1402.         Menu Bindings, accessed through drop-down menus (keyboard navigable)
  1403. ]]--
  1404. local _printers = { name = "Print"}
  1405. local _mChoices = { name = "File",
  1406.     { name = "New File", enabled = true, value = function() if checkSave() then constructNewDocument() end end },
  1407.     { name = "Open", enabled = true, value = function()
  1408.         if checkSave() then
  1409.             local _sp,_sn = displayFileBrowser("-l", 14, 24, 3)
  1410.             if _sp then
  1411.                 loadDocument(_sp)
  1412.                 sPath = _sp
  1413.                 sName = _sn
  1414.         end end end },
  1415.     { name = "Save", enabled = false, value = saveDocument },
  1416.     { name = "Save As", enabled = true, value = menuSaveAs },
  1417.     { name = "Print", enabled = false, value = _printers },
  1418.     { name = "About", enabled = true, value = displayAbout },
  1419.     { name = "Quit", enabled = true, value = function() if checkSave() then finished = true end end }
  1420. }
  1421. --[[ Updates the list of currently available printers and other menu options
  1422.      Params: none
  1423.      Returns: none
  1424. ]]--
  1425. local function updateMenu()
  1426.     --Again, horrible. My coding sucks.
  1427.     for i=1,6 do _printers[i] = nil end
  1428.     for _,_s in pairs(rs.getSides()) do
  1429.         if peripheral.isPresent(_s) and peripheral.getType(_s) == "printer" then
  1430.             table.insert(_printers, { name = _s, enabled = true, value = _s })
  1431.         end
  1432.     end
  1433.     _mChoices[3].enabled = sChange and sPath
  1434. end
  1435.  
  1436. --Performs the menu
  1437. local function performMenu()
  1438.     updateMenu()
  1439.     local _val = displayDropDown(2, 1, _mChoices)
  1440.     if _val == "New File" then
  1441.         constructNewDocument()
  1442.     elseif _val == "Quit" then
  1443.         finished = true
  1444.     elseif peripheral.getType(_val) == "printer" then
  1445.         _p = peripheral.wrap(_val)
  1446.         clearAndRedraw()
  1447.         local _ps,_pe,_cp = displayPrint(_p, math.ceil(w/2), 8)
  1448.         if not _ps then
  1449.             lscroll = true
  1450.             return
  1451.         end
  1452.         clearAndRedraw()
  1453.         for c=1,_cp do
  1454.             for i=_ps,_pe do
  1455.                 while _p.getPaperLevel() == 0 do
  1456.                     displayConfirmDialogue("Page "..i..": Check paper levels", "The paper tray is currently empty. Fill with paper and click here to continue.")
  1457.                 end
  1458.                 while _p.getInkLevel() == 0 do
  1459.                     displayConfirmDialogue("Page "..i..": Check ink levels", "The printer is out of ink. Insert more ink, and click here to continue.")
  1460.                 end
  1461.                 --There is no out-tray check... that I can tell at least.
  1462.                 while not _p:newPage() do
  1463.                     displayConfirmDialogue("Page "..i..": Check out tray", "The out tray may be full. Remove all pages and click here to continue.")
  1464.                 end
  1465.                 _p.setPageTitle((sName or "").." page "..i)
  1466.                 printPage(doc[i], _p)
  1467.                 _p:endPage()
  1468.             end
  1469.             if c<_cp then displayConfirmDialogue("Copy "..c.." Complete!", "Clear out tray and click here to continue") end
  1470.         end
  1471.        
  1472.         clearAndRedraw()
  1473.         displayConfirmDialogue("Print Complete!", "Printed ".._cp.." copies (total "..#doc.." page(s).)")
  1474.     end
  1475.     lscroll = true
  1476. end
  1477.  
  1478. --[[ A series of functions binded to keyboard shortcuts
  1479.      Parameters: none
  1480.      Returns: none
  1481. ]]--
  1482. local bindings = {
  1483.     --Moves us up. Hitting the top moves the cursor to the start of the line.
  1484.     --Most editors do this. I don't know why. Convention?
  1485.     [keys.up] = function()
  1486.         if currline > 1 then currline = currline - 1
  1487.         elseif currpage > 1 then
  1488.             currpage = currpage - 1
  1489.             currline = #doc[currpage]
  1490.         else
  1491.             currind = 1
  1492.             return
  1493.         end
  1494.         if currind > #doc[currpage][currline] then
  1495.             currind = math.max(1, #doc[currpage][currline] + 1)
  1496.         end
  1497.         fixCursorPos()
  1498.         checkYScroll()
  1499.     end,
  1500.     --Moves us down. Hitting the bottom moves cursor to end of the line.
  1501.     [keys.down] = function()
  1502.         if currline < #doc[currpage] then currline = currline + 1
  1503.         elseif currpage < #doc then
  1504.             currpage = currpage + 1
  1505.             currline = 1
  1506.         else
  1507.             currind = #doc[currpage][currline] + 1
  1508.             return
  1509.         end
  1510.         if currind > #doc[currpage][currline] then
  1511.             currind = math.max(1, #doc[currpage][currline] + 1)
  1512.         end
  1513.         fixCursorPos()
  1514.         checkYScroll()
  1515.     end,
  1516.     --Moves us left. Hitting and edge moves to the end of the last line.
  1517.     [keys.left] = function()
  1518.         if currind <= 1 then
  1519.             if currline > 1 then
  1520.                 currline = currline - 1
  1521.                 currind = #doc[currpage][currline] + 1
  1522.             elseif currpage > 1 then
  1523.                 currpage = currpage - 1
  1524.                 currline = #doc[currpage]
  1525.                 currind = #doc[currpage][currline] + 1
  1526.             end
  1527.         else
  1528.             currind = currind - 1
  1529.         end
  1530.         fixCursorPos()
  1531.         checkYScroll()
  1532.     end,
  1533.     --Moves us right. Hitting the edge moves to the start of the next line.
  1534.     [keys.right] = function()
  1535.         if currind >= #doc[currpage][currline] + 1 or
  1536.                 doc[currpage][currline]:sub(currind) == "\n" then
  1537.             if currline < #doc[currpage] then
  1538.                 currline = currline + 1
  1539.                 currind = 1
  1540.             elseif currpage < #doc then
  1541.                 currpage = currpage + 1
  1542.                 currline = 1
  1543.                 currind = 1
  1544.             end
  1545.         else
  1546.             currind = currind + 1
  1547.         end
  1548.         fixCursorPos()
  1549.         checkYScroll()
  1550.     end,
  1551.     --[[ Adds a newline character to the end of the line, and pushes everything down ]]
  1552.     [keys.enter] = function()
  1553.         --First we put whatever follows the cursor in a carry buffer
  1554.         local _cbuff = doc[currpage][currline]:sub(currind)
  1555.         doc[currpage][currline] = doc[currpage][currline]:sub(1,currind-1).."\n"
  1556.         _desl,_desp = -1,-1
  1557.         --We then push the carry buffer down each line in the document
  1558.         while nextLine() do
  1559.             --This remembers where our cursor needs to be when we're done
  1560.             if _desl == -1 or _desp == -1 then
  1561.                 _desl, _desp = currline, currpage
  1562.             end
  1563.             --We then swap out our buffer for the current line, to push down.
  1564.             local _ctemp = doc[currpage][currline]
  1565.             doc[currpage][currline] = _cbuff
  1566.             _cbuff = _ctemp
  1567.         end
  1568.         --We then drop in our last line
  1569.         if currline < pagelen then
  1570.             doc[currpage][currline+1] = _cbuff
  1571.         else
  1572.             doc[currpage+1] = { _cbuff }
  1573.         end
  1574.         --This updates our cursor back to where it should be
  1575.         if _desl ~= -1 and _desp ~= -1 then
  1576.             currind, currline, currpage = 1, _desl, _desp
  1577.         else
  1578.             --Called when we hit enter at the end of a document
  1579.             nextLine()
  1580.             currind = 1
  1581.         end
  1582.         paginateUnderflow()
  1583.         checkYScroll()
  1584.         sChange = true
  1585.     end,
  1586.     --Some more simple cursor manipulation keys
  1587.     [keys.home] = function() currind = 1 end,
  1588.     [keys["end"]] = function()
  1589.         currind = #doc[currpage][currline] + 1
  1590.         fixCursorPos()
  1591.     end,
  1592.     [keys.pageDown] = function()
  1593.         if currpage < #doc then
  1594.             currpage = currpage + 1
  1595.             currline = 1 currind = 1
  1596.             checkYScroll()
  1597.         end
  1598.     end,
  1599.     [keys.pageUp] = function()
  1600.         if currpage > 1 then
  1601.             currpage = currpage - 1
  1602.             currline = 1 currind = 1
  1603.             checkYScroll()
  1604.         end
  1605.     end,
  1606.     -- Some other core functions in the program
  1607.     [keys.backspace] = deleteCharacter,
  1608.     [keys.leftCtrl] = performMenu,
  1609.     [keys.tab] = function() testMode = not testMode end
  1610. }
  1611.  
  1612.  
  1613. --[[ Performs actions based on keyboard and mouse input
  1614.      Parameters: none
  1615.      Returns: none
  1616. ]]--
  1617. local function handleInput()
  1618.     local _,_cpy = term.getCursorPos()
  1619.     if _cpy > 1 then term.setCursorBlink(true) end
  1620.     local _id,_p1,_p2,_p3 = os.pullEvent()
  1621.     term.setCursorBlink(false)
  1622.     if _id == "char" then
  1623.         addCharacter(_p1)
  1624.     elseif _id == "key" then
  1625.         if bindings[_p1] then bindings[_p1]()
  1626.         elseif _p1 == keys.leftCtrl then finished = true end
  1627.     elseif _id == "mouse_scroll" then
  1628.         yscroll = yscroll - _p1
  1629.         if yscroll > 4 - _yoff then
  1630.             if currpage > 1 then
  1631.                 yscroll = h - pagelen - _yoff - 3
  1632.                 currpage = currpage - 1
  1633.                 currline = 1
  1634.                 currind = 1
  1635.             else yscroll = 4 - _yoff end
  1636.         elseif yscroll < h - pagelen - _yoff - 3 then
  1637.             if currpage < #doc then
  1638.                 yscroll = 4 - _yoff
  1639.                 currpage = currpage + 1
  1640.                 currline = 1
  1641.                 currind = 1
  1642.             else yscroll = h - pagelen - _yoff - 3 end
  1643.         end
  1644.         lscroll = true
  1645.     elseif _id == "mouse_click" then
  1646.         if _p3 < 6 and _p3 == 1 then
  1647.             performMenu()
  1648.         else
  1649.             currline = math.max(_p3 - _yoff - yscroll, 1)
  1650.             currline = math.min(currline, #doc[currpage])
  1651.             currind = math.max(_p2 - _xoff, 1)
  1652.             currind = math.min(currind, #doc[currpage][currline] + 1)
  1653.             if currind > 1 and doc[currpage][currline]:sub(currind-1) == "\n"
  1654.                 then currind = currind - 1 end
  1655.         end
  1656.         checkYScroll()
  1657.     end
  1658.     testWait()
  1659. end
  1660.  
  1661. if not term.isColour() then
  1662.     print("Currently, WYSIwrite is only supported on advanced computers")
  1663. end
  1664.  
  1665. local _tArgs = {...}
  1666. if _tArgs[1] then
  1667.     sName = _tArgs[1]
  1668.     sPath = shell.resolve(sName)
  1669.     if fs.exists(sPath) then loadDocument(sPath)
  1670.     else constructNewDocument(sName, sPath) end
  1671. else constructNewDocument() end
  1672. checkYScroll()
  1673. while not finished do
  1674.     --Basic printing stuff
  1675.     if lscroll then
  1676.         displayClearPage()
  1677.         lscroll = false
  1678.     end
  1679.     term.setBackgroundColour(colours.white)
  1680.     term.setTextColour(colours.black)
  1681.     printPage(doc[currpage], term, _xoff + 1, _yoff + yscroll)
  1682.     displayInterface()
  1683.     --Repositioning the cursor
  1684.     term.setTextColour(colours.black)
  1685.     term.setCursorPos(currind + _xoff, currline + _yoff + yscroll)
  1686.    
  1687.     handleInput()
  1688. end
  1689. term.setBackgroundColour(colours.black)
  1690. shell.run("clear")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement