· 8 years ago · Jul 21, 2018, 11:40 PM
1--[[
2 WYSIwrite
3 A WYSIWYG text editor for printing documents in ComputerCraft
4
5 Written by: Nitrogen Fingers
6 Printer detection modified by someone else
7]]--
8doc = {}
9
10--The width and height of the screen
11local w,h = term.getSize()
12--Whether or not the program is still running
13local finished = false
14
15--The length allowed for an individual line
16local linelen = 25
17--The lines allowed for an individual page
18local pagelen = 21
19
20--The currently viewed page
21local currpage = 1
22--The currently edited line
23local currline = 1
24--The currently index of the line edit
25local currind = 1
26
27--The amount of scrolling applied to the page on the Y.
28local yscroll = 0
29--The drawing offset
30local _xoff,_yoff = math.floor(w/2 - linelen/2),4
31--Whether or not a scroll or page change has occurred
32local lscroll = true
33--Whether or not the player is currently in a drop down menu
34local inDropDown = false
35
36--Whether or not whitespace characters are displayed
37local whitespaceVis = false
38--Whether or not a change has been made to the document since last save
39local sChange = false
40--The path currently being used to save to
41local sPath = nil
42--The file's name
43local sName = nil
44
45--The default directory for the write backup; this can be changed if you don't like it
46local bPath = "/.writebackup"
47--The backup timer, and its default length
48local bTimer, bLength = nil, 5
49
50--Alignment options, and a global test variable
51local ALIGNLEFT, ALIGNCENTER, ALIGNRIGHT = 0,1,2
52local testAlign = ALIGNLEFT
53
54--The list of all possible actions
55local ACTIONS = {
56 ADDCHAR = 0;
57 DELETECHAR = 1;
58 ADDBREAK = 2;
59}
60--The list of all actions performed. They contain:
61 --page,line,index as the location of the event
62 --type from the ACTIONS table
63 --value is the character or characters that were added/removed
64local undolist = {}
65local redolist = {}
66--The timer that determines which 'actions' are clumped together with undo and redo
67local actionTimer = nil
68--How many seconds of inactivity before the action is classified as separate by undo
69local actionDelay = 0.5
70
71--Menu Bindings, initialized at bottom of program
72
73local _printers = { name = "Print" }
74local _iChoices = { name = "Insert" }
75local _aChoices = { name = "Align" }
76local _mChoices = { name = "File", x = 2, y = 1 }
77local _eChoices = { name = "Edit", x = 4 + #_mChoices.name, y = 1 }
78
79--For me
80local testMode = false
81local _tro = false
82local _suppress = false
83
84--For my testing purposes.
85function testPrint(_str)
86 if testMode then
87 if term.getCursorPos() ~= 1 then term.setCursorPos(1,1) end
88 term.setBackgroundColour(colours.white)
89 term.setTextColour(colours.red)
90 if not _suppress then print(_str) end
91 lscroll = true
92 _tro = true
93 end
94end
95function testWait()
96 if testMode and _tro then os.pullEvent("key") end
97 _tro = false
98end
99function testSuppress() _suppress = true end
100function testRelax() _suppress = false end
101
102--Returns whether or not a token is considered a newline for printing and pagination purposes
103function nl(str) return str == "\n" or str == string.char(17) end
104
105--I'm getting a lot of mileage out of this split function
106function split(str, pattern)
107 local t = { }
108 local fpat = "(.-)" .. pattern
109 local last_end = 1
110 local s, e, cap = str:find(fpat, 1)
111 while s do
112 if s ~= 1 or cap ~= "" then
113 table.insert(t,cap)
114 end
115 last_end = e+1
116 s, e, cap = str:find(fpat, last_end)
117 end
118 if last_end <= #str then
119 cap = str:sub(last_end)
120 table.insert(t, cap)
121 end
122 return t
123end
124
125--Returns whether or not the given string is break terminated.
126local function hasBreakEnd(_line)
127 return _line and _line:find("[\n\f]") ~= nil
128end
129
130
131--[[ Gets the previous file in the path, if there is one ]]--
132local function getPreviousDir(_path)
133 if _path == "" or _path == "/" then return path
134 else
135 _path = _path:reverse()
136 return _path:sub(_path:find("/.*")+1):reverse()
137 end
138end
139
140--[[ Constructs an empty document that can be edited. Empty documents contain a
141 single page, with one line that cannot be deleted.
142 Parameters: none
143 Returns: none
144]]--
145local function constructNewDocument(_sName, _sPath)
146 --pages
147 doc = {
148 --lines
149 {
150 ""
151 }
152 }
153 currpage = 1
154 currline = 1
155 currind = 1
156 paginateOverflow()
157 sPath = _sPath
158 sName = _sName
159 sChange = false
160end
161
162--[[ Constructs a document from an existing source
163 Parameters: none
164 Returns: none
165]]--
166local function loadDocument(_path)
167 doc = {
168 { }
169 }
170 assert(fs.exists(_path), _path)
171 local _file = fs.open(_path, "r")
172 local _line = _file.readLine()
173 while _line do
174 --Form feed check
175 _lineEndIndex = 1
176 while true do
177 local _fco = _line:find("\f", _lineEndIndex)
178 if not _fco then break end
179 if #doc[#doc] < pagelen then
180 if _lineEndIndex == 1 then
181 table.insert(doc[#doc], _line:sub(_lineEndIndex, _fco))
182 else
183 doc[#doc][#doc[#doc]] = _line:sub(_lineEndIndex, _fco)
184 end
185 else
186 table.insert(doc, { _line:sub(_lineEndIndex, _fco)})
187 end
188 _lineEndIndex = _fco + 1
189 currpage = #doc
190 currline = #doc[#doc]
191 paginateOverflow()
192 end
193 --Rest of line
194 if #doc[#doc] < pagelen then
195 if _lineEndIndex == 1 then
196 table.insert(doc[#doc], _line:sub(_lineEndIndex).."\n")
197 else
198 --Replacing the empty strings added by the paginator on form feeds.
199 doc[#doc][#doc[#doc]] = _line:sub(_lineEndIndex).."\n"
200 end
201 else
202 table.insert(doc, { _line:sub(_lineEndIndex).."\n" })
203 end
204 currpage = #doc
205 currline = #doc[#doc]
206 paginateOverflow()
207
208 _line = _file.readLine()
209 if not _line then
210 local _od = doc[#doc][#doc[#doc]]
211 doc[#doc][#doc[#doc]] = _od:sub(1,#_od-1)
212 end
213 end
214 _file:close()
215 if not doc[1][1] then doc[1][1] = "" end
216 currpage = 1
217 currline = 1
218 currind = 1
219 sChange = false
220end
221
222--[[ "Depaginates" the current document into straight lines and saves the
223 output to a file location for standard viewing
224 Parameters: _spath:string= the location to save the file
225 Returns: none
226]]--
227local function saveDocument(_path)
228 if not _path then _path = sPath end
229 local _lines = { "" }
230
231 local _opi,_oli = currpage, currline
232 currpage = 1
233 currline = 0
234 while nextLine() do
235 local _cline = doc[currpage][currline]
236 if _cline:sub(#_cline) == "\n" then
237 _lines[#_lines] = _lines[#_lines].._cline:sub(1,#_cline-1)
238 table.insert(_lines, "")
239 else
240 _lines[#_lines] = _lines[#_lines].._cline
241 end
242 end
243 --Remove that empty 'newline' we include
244 if _lines[#_lines] == "" then table.remove(_lines, #_lines) end
245 local _file = fs.open(_path, "w")
246 for i=1,#_lines do
247 _l = _lines[i]
248 _file.writeLine(_l)
249 end
250 _file.close()
251 currpage, currline = _opi, _oli
252 if _path ~= bPath then sChange = false end
253 testWait()
254end
255
256--A quick function to let the program know there have been changes to the file
257local function setUpdated()
258 sChange = true
259 bTimer = os.startTimer(bLength)
260end
261
262--[[ Updates the document scroll to match the movement of the cursor.
263 Parameters: none
264 Returns: none
265]]--
266local function checkYScroll()
267 if yscroll + currline + _yoff > h - 3 then
268 yscroll = h - 3 - currline - _yoff
269 lscroll = true
270 elseif yscroll + currline + _yoff < 3 then
271 yscroll = 3 - currline - _yoff
272 lscroll = true
273 end
274end
275
276--[[ Returns the current amount of whitespace added to the front of the line
277 (in addition to spaces etc.) as required by the alignment of the paragraph.
278 Params: _pagenum = the page number being printed
279 _linenum = the linenum being printed
280 Returns:int = the number of additional spaces from the left before the line starts
281]]--
282function getWhitespaceLength(_pagenum, _linenum)
283 local _page = doc[_pagenum]
284 _line = _page[_linenum]:gsub("[\n\f]", "")
285 if testAlign == ALIGNRIGHT then
286 return linelen-#_line
287 elseif testAlign == ALIGNCENTER then
288 return math.floor(linelen/2) - math.floor(#_line/2)
289 else
290 return 0
291 end
292end
293
294--[[ Lowers the current line by 1, if possible
295 Parameters: int:bookean = whether or not to include null lines
296 Returns: true if there is a next line; false otherwise
297]]--
298function nextLine(_inc)
299 --Lines are checked if we are below the pagelen and not on the last page
300 if (_inc and (currline < pagelen and currpage < #doc) or (currline < #doc[currpage]
301 and currpage == #doc)) or (not _inc and currline < #doc[currpage]) then
302 currline = currline + 1
303 elseif currpage < #doc then
304 currpage = currpage + 1
305 currline = 1
306 else
307 return false
308 end
309 return true
310end
311
312--[[ Ensures the cursor is in a valid position. Being outside the size of
313 a string or before a forbidden character like a newline or tab are fixed.
314 Parameters: none
315 Returns: none
316]]--
317local function fixCursorPos()
318 if not doc[currpage] then
319 currpage = #doc
320 currline = #doc[#doc]
321 end
322 if not doc[currpage][currline] then
323 if currline > pagelen then
324 currline = pagelen
325 else
326 --I don't know if this often occurs...
327 currline = #doc[currpage]
328 end
329 currind = #doc[currpage][currline] + 1
330 end
331 if currind < 1 then currind = 1
332 elseif currind > #doc[currpage][currline] + 1 then
333 currind = #doc[currpage][currline] + 1
334 end
335 if currind > #doc[currpage][currline] and hasBreakEnd(doc[currpage][currline]) then
336 currind = currind - 1
337 end
338end
339
340--[[ Goes to the previous line, if possible
341 Parameters: none
342 Returns: true if there is a previous line; false otherwise
343]]--
344local function lastLine()
345 if currline > 1 then
346 currline = currline - 1
347 elseif currpage > 1 then
348 currpage = currpage - 1
349 currline = pagelen
350 else return false end
351 return true
352end
353
354--[[ Gets the previous line in the document to the one provided
355 Parameters: _page: the page in question (default to currpage)
356 _line: the line in question (default to currline)
357 Returns: string=The previous line, or nil if on the first line
358]]--
359local function getPrevLine(_page, _line)
360 if not _page or not _line then _page = currpage _line = currline end
361 if _page == 1 and _line == 1 then return nil
362 elseif _line == 1 then _page = _page - 1 _line = #doc[_page]
363 else _line = _line - 1 end
364 return doc[_page][_line]
365end
366
367--[[ Gets the next line in the document to the one provided
368 Parameters: _page: the page in question (default to currpage)
369 _line: the line in question (default to currline)
370 Returns: string=The next line, or nil if on the last line
371]]--
372local function getNextLine(_page, _line)
373 if not _page or not _line then _page = currpage _line = currline end
374 if _page == #doc and _line == #doc[#doc] then return nil
375 elseif _line == #doc[_page] then _line = 1 _page = _page + 1
376 else _line = _line + 1 end
377 return doc[_page][_line]
378end
379
380--[[ A helper that returns whether or not the input line is the last in the
381 document (the last line has a few special rules associated with it)
382 Parameters: _page: the page in question (default to currpage)
383 _line: the line in question (default to currline)
384 Returns: true if the last line in document; false otherwise
385]]--
386local function isLastLine(_page, _line)
387 if not _page or not _line then _page = currpage _line = currline end
388 return _page == #doc and _line == #doc[#doc]
389end
390
391--[[Inserts a character to function as a line break. These can be any form of
392 character which should end the line and disallow further edits past it.
393 By current implementation, the break is inserted at the point of the cursor.
394 Params: _sym:character = the kind of break to insert. Currently, \n and
395 \f are supported.
396]]--
397function insertLineBreak(_sym)
398 --First we put whatever follows the cursor in a carry buffer
399 local _cbuff = doc[currpage][currline]:sub(currind)
400 doc[currpage][currline] = doc[currpage][currline]:sub(1,currind-1).._sym
401 _desl,_desp = -1,-1
402 --We then push the carry buffer down each line in the document
403 while nextLine(true) do
404 --This remembers where our cursor needs to be when we're done
405 if _desl == -1 or _desp == -1 then
406 _desl, _desp = currline, currpage
407 end
408 --First line form feed test
409 if _cbuff:find("\f") and currline == 1 then
410 table.insert(doc, currpage, { _cbuff })
411 _cbuff = nil
412 break
413 end
414 --We then swap out our buffer for the current line, to push down.
415 local _ctemp = doc[currpage][currline]
416 doc[currpage][currline] = _cbuff
417 _cbuff = _ctemp
418 --Special form feed test
419 if doc[currpage][currline]:find("\f") then
420 break
421 end
422 end
423 --We then drop in our last line
424 if _cbuff then
425 if currline < pagelen then
426 doc[currpage][currline+1] = _cbuff
427 else
428 doc[currpage+1] = { _cbuff }
429 end
430 end
431 --This updates our cursor back to where it should be
432 if _desl ~= -1 and _desp ~= -1 then
433 currind, currline, currpage = 1, _desl, _desp
434 else
435 --Called when we hit enter at the end of a document
436 nextLine()
437 currind = 1
438 end
439 testWait()
440 paginateUnderflow()
441 testWait()
442 checkYScroll()
443 setUpdated()
444end
445
446--[[ Adds a sequence of text to the current line and index
447 Parameters: val:String = the character to be inserted
448 Returns: none
449]]--
450local function addCharacter(_val)
451 local _cltext = doc[currpage][currline]
452 if currind > #_cltext then
453 _cltext = _cltext.._val
454 else
455 _cltext = _cltext:sub(1, currind-1).._val.._cltext:sub(currind)
456 end
457 doc[currpage][currline] = _cltext
458 currind = currind + #_val
459 paginateOverflow()
460 -- Space actions must also be underflowed; they may have broken a word.
461 if _val == " " then paginateUnderflow() end
462 checkYScroll()
463 setUpdated()
464end
465
466--[[ Removes a character from the current line and index
467 Parameters: none
468 Returns:String = the character that was deleted
469]]--
470function deleteCharacter()
471 --We first move our cursor to the character we want to delete
472 if currind == 1 then
473 if currline > 1 then currline = currline - 1
474 elseif currpage > 1 then
475 currpage = currpage - 1
476 currline = #doc[currpage]
477 else return end
478 currind = #doc[currpage][currline]
479 else currind = currind - 1 end
480 --Then create 2 'snips'; before and after the character
481 local _cline = doc[currpage][currline]
482 local _fsnip,_asnip = "",""
483 if currind > 1 then _fsnip = _cline:sub(1,currind-1) end
484 if currind < #_cline then _asnip = _cline:sub(currind+1) end
485 --We patch our snips, remove dead lines and do a cleanup
486 doc[currpage][currline] = _fsnip.._asnip
487 if doc[currpage][currline] == "" then
488 _lline = getPrevLine()
489 if not _lline or not hasBreakEnd(_lline) then
490 removeLine(currpage, currline)
491 end
492 end
493 paginateUnderflow()
494 fixCursorPos()
495 checkYScroll()
496 setUpdated()
497 return _cline:sub(currind,currind)
498end
499
500--[[Undoes the first action in the provided list, and pushes it's inverse to the second.
501 Parameters: _remList: the list to reverse the action
502 _addList: the list to push the move to
503 Returns: nil
504]]--
505local function undoAction(_remList,_addList)
506 if #_remList == 0 then return end
507 local _actionList,_newList = table.remove(_remList, #_remList), {}
508 testPrint("List has "..#_actionList.." elements")
509 for _,_action in ipairs(_actionList) do
510 testPrint("At "..currind.." doing ".._action.type.." <".._action.value..">")
511 currpage,currline,currind = _action.page, _action.line, _action.index
512 if _action.type == ACTIONS.ADDCHAR then
513 currind = currind + 1
514 deleteCharacter()
515 _action.type = ACTIONS.DELETECHAR
516 elseif _action.type == ACTIONS.DELETECHAR then
517 if _action.value == "\n" or _action.value == "\f" then
518 insertLineBreak(_action.value)
519 _action.type = ACTIONS.ADDBREAK
520 else
521 addCharacter(_action.value)
522 _action.type = ACTIONS.ADDCHAR
523 end
524 elseif _action.type == ACTIONS.ADDBREAK then
525 currind = currind + 1
526 deleteCharacter()
527 _action.type = ACTIONS.DELETECHAR
528 end
529 table.insert(_newList, 1, _action)
530 end
531 table.insert(_addList, _newList)
532end
533
534--[[Adds a new undoable action to the current list
535 Parameters: _ind: the index the cursor needs to be at
536 _type: the type of action
537 _value: the character impacted by the action
538
539]]--
540local function addUndoableAction(_ind, _type, _value)
541 if not actionTimer then
542 table.insert(undolist, {})
543 end
544 actionTimer = os.startTimer(actionDelay)
545 table.insert(undolist[#undolist], 1, {
546 page = currpage;
547 line = currline;
548 index = _ind;
549 type = _type;
550 value = _value;
551 })
552end
553
554--[[Returns the page and line by adding the given linecount. Allows going past
555 the bounds of the document, and can be used in reverse (but always stops at 1,1)
556 Params: _page:int = the page number to start at
557 _line:int = the line number to start at
558 _linecount:int = the number of lines to move forwards or backwards
559 Returns:int = the new page
560 int = the new line
561]]--
562function moveBy(_page, _line, _linecount)
563 _line = _line + _linecount
564 while _line > pagelen do
565 _line = _line - pagelen
566 _page = _page + 1
567 end
568 return _page, _line
569end
570
571--[[Takes a line with a page break terminator and determines whether or
572 not the document complies with the check. Only once per update now :)
573 Params: _pagenum:int = the page number of the line
574 _linenum:int = the line number
575 Returns: true if a change was made, false otherwise
576 int,int: the new page and line number of the NEXT page break;
577 this will need to be evaluated.
578]]--
579function performPageBreakCheck(_pagenum, _linenum)
580 if not _pagenum and not _linenum then _pagenum, _linenum = 1, 1 end
581
582 --For all subsequent pages, a reverse parse to ensure there are no null lines
583 --between them and the next page (except where there are page breaks of course)
584 local _docEnd = #doc
585 local _nullSpace = {}
586 local _floatingLines = {}
587 _p = _pagenum
588 while _p <= #doc do
589 local _breakPassed = false
590 local _textOnPage = true
591 for _l=_linenum,pagelen do
592 local _line = doc[_p][_l]
593 if not _line then
594 if #_floatingLines > 0 and not _breakPassed then
595 doc[_p][_l] = table.remove(_floatingLines, 1)
596 if type(_floatingLines[1]) == "number" then
597 currind = table.remove(_floatingLines, 1)
598 currpage, currline = _p, _l
599 end
600 _textOnPage = true
601 if doc[_p][_l]:sub(#doc[_p][_l]) == "\f" then
602 _breakPassed = true
603 _nullSpace = {}
604 end
605 elseif not _breakPassed then
606 table.insert(_nullSpace, {_p, _l})
607 end
608 else
609 if _breakPassed then
610 table.insert(_floatingLines, _line)
611 if _p == currpage and _l == currline then
612 table.insert(_floatingLines, currind)
613 end
614 doc[_p][_l] = nil
615 elseif #_floatingLines > 0 then
616 table.insert(_floatingLines, _line)
617 doc[_p][_l] = table.remove(_floatingLines, 1)
618 if type(_floatingLines[1]) == number then
619 currind = table.remove(_floatingLines[1])
620 currpage, currline = _p, _l
621 end
622 elseif #_nullSpace > 0 then
623 local _ns = table.remove(_nullSpace, 1)
624 doc[_ns[1]][_ns[2]] = _line
625 doc[_p][_l] = nil
626 if currpage == _p and _currline == _l then
627 currpage, currline = _ns[1], _ns[2]
628 end
629 --Now this MAY be unsafe, but I can't recreate the issue on paper
630 if _line:sub(#_line) == "\f" then
631 _breakPassed = true
632 _nullSpaces = {}
633 end
634 if _ns[1] == _p then _textOnPage = true end
635 elseif _line:sub(#_line) == "\f" then
636 _breakPassed = true
637 _textOnPage = true
638 _nullSpaces = {}
639 end
640 end
641 end
642
643 if not _textOnPage and _p > 1 then
644 table.remove(doc, _p)
645 _p = _p - 1
646 end
647 if _p == #doc and #_floatingLines > 0 then
648 table.insert(doc, {})
649 end
650 if _p == #doc and _breakPassed and #_floatingLines == 0 then
651 table.insert(doc, {})
652 table.insert(_floatingLines, "")
653 end
654
655 testWait()
656 _docEnd = #doc
657 _p = _p + 1
658 _linenum = 1
659 end
660 lscroll = true
661end
662
663--[[ Removes an entire line from the document, and shifts all previous
664 lines up
665 Parameters: pagenum:int = the page the line is on
666 linenum:int = the line to remove
667 Returns: none
668]]--
669function removeLine(_pagenum, _linenum)
670 _linenum = _linenum + 1
671 if _linenum > pagelen then _linenum = 1 _pagenum = _pagenum + 1 end
672 local _lp = ""
673 --To delete, we copy each line to replace the previous
674 while _pagenum <= #doc and _linenum <= #doc[_pagenum] do
675 local _cbuff = doc[_pagenum][_linenum]
676 if _linenum == 1 then
677 doc[_pagenum-1][pagelen] = _cbuff
678 --Nothing follows a form feed, so the last page was empty. Delete!
679 if _cbuff:find("\f") then
680 table.remove(doc, _pagenum)
681 assert(doc[_pagenum], "Deleted last page (ff) in remove line")
682 return
683 end
684 else
685 doc[_pagenum][_linenum-1] = _cbuff
686 --Nothing follows a form feed so we stop removing here.
687 if _cbuff:find("\f") then doc[_pagenum][_linenum] = nil return end
688 end
689 if _linenum == pagelen then _linenum = 1 _pagenum = _pagenum + 1
690 else _linenum = _linenum + 1 end
691 _lp = _cbuff
692 end
693 --And black out the very last so we know nothing is there.
694 _linenum = _linenum - 1
695 if (_pagenum == 1 and _linenum == 1) or _lp:sub(#_lp) == "\n" then
696 doc[#doc][#doc[#doc]] = ""
697 else doc[#doc][#doc[#doc]] = nil end
698 if #doc[#doc] == 0 and #doc ~= 1 then
699 table.remove(doc, #doc)
700 end
701 --So long as we still have something in the doc
702 if #doc[1] == 0 then doc[1][1] = "" end
703end
704
705--[[ Inserts a line of text into a specified index. The replaced line and
706 all subsequent lines are pushed down the document
707 Parameters: _pagenum:int = the page the line is one
708 _linenum:int = the line in which to insert
709 _text:string = the text to insert. Defaults to a newline.
710]]--
711function insertLine(_pagenum, _linenum, _text)
712 if not _text then _text = "\n" end
713 local _pi,_li = #doc, #doc[#doc] + 1
714 if _li > linelen then _pi = _pi + 1 _li = 1 end
715 if not doc[_pi] then doc[_pi] = { } end
716 local _icount = 0
717 while _pi > _pagenum or _li > _linenum do
718 doc[_pi][_li] = getPrevLine(_pi,_li)
719 _li = _li - 1
720 if _li == 0 then _li = pagelen _pi = _pi - 1 end
721 _icount = _icount + 1
722 end
723 doc[_pagenum][_linenum] = _text
724end
725
726
727--[[ Updates pagination at the point following the cursor for overflow as
728 a result of an insertion edit action. The overflow by default only
729 checks so far as the previous insertion but can be set to check the entire
730 program through a switch.
731 Returns: none
732]]--
733function paginateOverflow()
734 --Whether or not we need to keep paginating
735 local _pfollow = false
736 --The line we start at on this page
737 local _pi,_nli = currpage, currline
738 --Whether or not we've done an insertion, and need to perform another check
739 local _icheck = false
740
741 while _pi <= #doc do
742 _li = _nli
743 while _li <= #doc[_pi] do
744 local _cltext = doc[_pi][_li]
745 --First we check to see if it's a carry-on line
746 local _conn = 0
747 local _nl = getNextLine(_pi,_li)
748 if _nl and _cltext:sub(#_cltext) ~= " " and
749 _nl:sub(1,1) ~= " " then
750 _conn = _nl:find(" ")
751 if not _conn then _conn = _nl:find("[\n\f]") end
752 if not _conn then _conn = #_nl
753 else
754 _conn = _conn - 1
755 end
756 end
757 --And a bit of leniency for newlines (they don't print anyway)
758 if hasBreakEnd(_cltext) then _conn = -1 end
759 --If the line is shorter than the line length, we are done
760 if (#_cltext + _conn <= linelen and not _icheck)
761 or #_cltext == 0 then
762 _pfollow = true
763 break
764 end
765 _icheck = false
766
767 --Next check is for form feeds stuck between lines, we space them out
768 local _nsp = -1
769 local _ffline = _cltext:find("\f")
770 if _ffline and _ffline <= linelen then _nsp = _ffline end
771
772 --Otherwise we find the nearest space to the edge of the document
773 if _nsp == -1 then
774 for _nsi = linelen, 1, -1 do
775 if _cltext:sub(_nsi,_nsi) == " " then
776 _nsp = _nsi
777 break
778 end
779 end
780 end
781 --The excess string is carried to the front of the next line or page
782 --Notice the space stays if there is more than one word on that line
783 if _nsp == -1 then _nsp = linelen end
784 doc[_pi][_li] = doc[_pi][_li]:sub(1,_nsp)
785 _carrystr = _cltext:sub(_nsp+1)
786 if _li == pagelen then
787 if _pi == #doc then
788 doc[_pi+1] = { _carrystr }
789 --Both newlines and form feeds must be carried, and cannot overflow.
790 elseif hasBreakEnd(_carrystr) then
791 insertLine(_pi+1,1,_carrystr)
792 _icheck = true
793 else
794 doc[_pi+1][1] = _carrystr..doc[_pi+1][1]
795 end
796 elseif _li == #doc[_pi] then
797 doc[_pi][_li+1] = _carrystr
798 --Newlines must have a following space in addition to this.
799 elseif _carrystr:sub(#_carrystr) == "\n" then
800 insertLine(_pi,_li+1,_carrystr)
801 _icheck = true
802 elseif _carrystr:sub(#_carrystr) == "\f" then
803 assert(false, "Carry form feed at ".._pi..",".._li)
804 else
805 doc[_pi][_li+1] = _carrystr..doc[_pi][_li+1]
806 end
807 --If this pagination affected our cursor, we move down a line
808 if _pi == currpage and _li == currline and
809 currind > #doc[currpage][currline] then
810 currind = currind - #doc[currpage][currline]
811 if currline == pagelen then
812 currline = 1
813 currpage = currpage + 1
814 else
815 currline = currline + 1
816 end
817 end
818 _li = _li + 1
819 testWait()
820 end
821 if _pfollow then break end
822 _nli = 1
823 _pi = _pi + 1
824 end
825 performPageBreakCheck()
826end
827
828--[[ Updates pagination at the point following the trailing line from the
829 cursor for underflow as a result of a delete edit action.
830 The pagination is optimized to only go so far as it has to when making
831 changes, but can cover the entire document through a switch.
832 Parameters: _cflag:bool= set to true to check entire document
833 Returns: none
834]]--
835function paginateUnderflow(_cflag)
836 --Whether or not we need to keep paginating
837 local _pfollow = false
838 --Whether or not a vulnerable underflow occurred. These are characterized
839 --as underflows that leave a partial word behind.
840 local _vocc = false
841 --How many pagination checks we need to run. It's a minimum of 2
842 --if we're on the same line
843 local _pcount = 3
844
845 --First step, we check out how the page breaks have modified things
846 performPageBreakCheck()
847
848 --The line we start at on this page
849 local _pi,_nli = currpage, currline
850 if _cflag then _pi,_nli = 1,1 end
851 --Our comparataor line
852 local _cpline = doc[_pi][_nli]
853 --The last character of the second to last line
854 local _llsnip = nil
855 --Where do we start, here or next line?
856 if _pi == 1 and _nli == 1 then _nli = _nli + 1 _pcount = _pcount - 1
857 elseif _nli == 1 then
858 _cpline = doc[_pi-1][#doc[_pi-1]]
859 else _cpline = doc[_pi][_nli-1] end
860
861 while _pi <= #doc do
862 local _li = _nli
863 while _li <= #doc[_pi] do
864 _llsnip = _cpline:sub(#_cpline)
865 local _cline = doc[_pi][_li]
866 local _nsp = -1
867 local _conn = 0
868 if hasBreakEnd(_cline) then _conn = -1 end
869 --We search for a word that will fit on the previous line
870 for _nsi = linelen - #_cpline,1,-1 do
871 if _cline:sub(_nsi,_nsi) == " " then
872 _nsp = _nsi
873 --Because we don't use it here
874 _conn = 0
875 break
876 end
877 end
878 --Single words short enough can be moved too
879 if #_cline+_conn <= linelen - #_cpline then
880 _nsp = #_cline+_conn
881 --A little exception in the event we leave our cursor behind
882 end
883 --And if there's no space at the end of the line, it moves back too
884 if _nsp == -1 and _cpline:sub(#_cpline) ~= " "
885 and not hasBreakEnd(_cpline) then
886 _nsp = linelen - #_cpline
887 --Also unused here
888 _conn = 0
889 --This however now requires an overflow check
890 _vocc = true
891 end
892
893 --If no such word exists, we're done
894 --Also, if our comparator line has a break, underflow is disallowed
895 --NOTE: Hideous code. This could be done far better.
896 if (_nsp == -1 or hasBreakEnd(_cpline)) then
897 if _pi ~= currpage and _li ~= currline and _pcount == 0 then
898 _pfollow = true
899 break
900 end
901 else
902 --Otherwise we snip our words from the current line,
903 local _snip = _cline:sub(1,_nsp-_conn)
904 doc[_pi][_li] = _cline:sub(_nsp+1-_conn)
905 --then stick the snip on the next line (lazy coding but meh)
906 if _li > 1 then doc[_pi][_li-1] = doc[_pi][_li-1].._snip
907 else
908 doc[_pi-1][#doc[_pi-1]] = doc[_pi-1][#doc[_pi-1]].._snip
909 end
910 --If we performed a deletion on the cursor line line and the
911 --cursor is within the clip, we must move it
912 if _pi == currpage and _li == currline and currind <= _nsp-_conn then
913 currind = currind + #_cpline
914 if _li == 1 then
915 currpage = currpage -1
916 currline = pagelen
917 else
918 currline = currline - 1
919 end
920 end
921 end
922 --Remove any excess lines
923 local _slsnip = getPrevLine(#doc, #doc[#doc])
924 if doc[_pi][_li] == "" and not(isLastLine(_pi, _li)
925 and hasBreakEnd(_slsnip)) then
926 --Remove line was not moving cursor. This needs to be fixed in later versions.
927 if _pi > currpage or (_pi == currpage and currline >= _li) then
928 --Our exception case; cursor is at EOF
929 if _pi == #doc and _li == #doc[_pi] then
930 removeLine(_pi,_li)
931 fixCursorPos()
932 else
933 removeLine(_pi,_li)
934 end
935 else
936 removeLine(_pi,_li)
937 end
938 _li = _li - 1
939 end
940 --Update our variables, and we're done here.
941 if not doc[_pi] or not doc[_pi][_li] then break end
942 _cpline = doc[_pi][_li]
943 _li = _li + 1
944 _pcount = math.max(_pcount-1, 0)
945 end
946 if _pfollow then break end
947 _nli = 1
948 _pi = _pi + 1
949 end
950 --Unsafe carryovers have to check for overflow pagination.
951 if _vocc then
952 paginateOverflow()
953 end
954end
955
956--[[ Draws the 'whitespace' surrounding the page and the menu. This includes both
957 page-display features like the margins around the page and borders to make it more
958 distinct.
959 Parameters: none
960 Returns: none
961]]--
962local function displayClearPage()
963 --Drawing the background
964 term.setBackgroundColour(colours.lightBlue)
965 term.clear()
966 term.setBackgroundColour(colours.grey)
967 --Adding a little border to the page to make it more distinct
968 local _ydpos = _yoff + yscroll - 1
969 if _ydpos > 0 and _ydpos <= h then
970 term.setCursorPos(_xoff + 1, _ydpos)
971 term.write(string.rep(" ", 2 + linelen))
972 term.setBackgroundColour(colours.white)
973 term.setCursorPos(_xoff, _ydpos + 1)
974 term.write(string.rep(" ", 2 + linelen))
975 end
976 if _ydpos + pagelen + 2 > 0 and _ydpos + pagelen + 2 <= h then
977 term.setBackgroundColour(colours.white)
978 term.setCursorPos(_xoff, _ydpos + pagelen + 2)
979 term.write(string.rep(" ", 2 + linelen))
980 end
981 for i = math.max(0, _ydpos), math.min(h, pagelen + yscroll + _yoff) do
982 if i > _ydpos or i == 0 then
983 term.setBackgroundColour(colours.white)
984 term.setCursorPos(_xoff, i)
985 term.write(" ")
986 term.setCursorPos(_xoff + linelen + 1, i)
987 term.write(" ")
988 else
989 term.setCursorPos(_xoff + linelen + 2, i)
990 end
991 term.setBackgroundColour(colours.grey)
992 term.write(" ")
993 end
994end
995
996--[[ Draws the currently selected page. This prints exactly the contents of the page and
997 whitespace to fill out the gaps as necessary.
998 Parameters: page:The page of the document to print
999 _out: The output construct
1000 _x:int = the x offset for the page to be printed at
1001 _y:int = the y offset for the page to be printed at
1002 Returns: none
1003]]--
1004function printPage(pagenum, _out, _x, _y)
1005 page = doc[pagenum]
1006 if not _x or not _y then _x = 1 _y = 0 end
1007 --Displaying the page itself, both text and no text
1008 for i=1,pagelen do
1009 local _ydpos = i + _y
1010 --This little optimization only works if _out == term, so it's not in for now.
1011 --if _out == term and _ydpos > 1 and _ydpos <= h then
1012 _out.setCursorPos(_x, i + _y)
1013 if i > 0 and i <= #page then
1014 local _wsEnd = hasBreakEnd(page[i])
1015 local _disp = string.gsub(page[i], "\n"," ")
1016 --Depending on the alignment, we add whitespace to make up the difference.
1017 local _wchar = " "
1018 if testMode then
1019 term.setTextColour(colours.red)
1020 _wchar = "-"
1021 end
1022 term.write(string.rep(_wchar, getWhitespaceLength(pagenum,i)))
1023 -- Our whitespace display function
1024 if (whitespaceVis or testMode) and _out == term then
1025 for _c=1,#page[i] do
1026 local _chr = page[i]:sub(_c,_c)
1027 if _chr == " " then
1028 term.setTextColour(colours.red)
1029 term.write(".")
1030 elseif _chr == "\n" then
1031 term.setTextColour(colours.red)
1032 term.write(">")
1033 elseif _chr == "\f" then
1034 term.setTextColour(colours.red)
1035 term.write("V")
1036 else
1037 term.setTextColour(colours.black)
1038 term.write(_chr)
1039 end
1040 end
1041 term.setTextColour(colours.black)
1042 else
1043 --Bah. This has to change. Forget nice code. This program is one enormous, hideous hack.
1044 _out.write(_disp)
1045 end
1046 _out.write(string.rep(" ", math.max(linelen - #_disp -
1047 getWhitespaceLength(pagenum, i), 0)))
1048 --Another hack. Exactly what I am....
1049 if _out == term and not _wsEnd then _out.write(" ") end
1050 else
1051 _out.write(string.rep(" ", linelen))
1052 end
1053-- end
1054 end
1055end
1056
1057
1058 --[[ Interface malarky ]]--
1059
1060--[[ A presently mostly empty function that displays tools and information about the
1061 current document for the user
1062 Parameters: none
1063 Returns: none
1064]]--
1065local function displayInterface()
1066 term.setBackgroundColour(colours.lightBlue)
1067 term.setTextColour(colours.black)
1068 --A little interface feature to tell the user what page they're viewing
1069 local _ppos = string.rep(" ",math.floor(math.log10(#doc)) - math.floor(math.log10(currpage)))
1070 ..currpage.." of "..#doc
1071 term.setCursorPos(w-#_ppos-1,h)
1072 term.setTextColour(colours.black)
1073 term.write(_ppos)
1074 term.setCursorPos(1,1)
1075 term.setBackgroundColour(colours.lightGrey)
1076 if testMode then term.setBackgroundColour(colours.red) end
1077 term.setTextColour(colours.black)
1078 term.clearLine()
1079
1080 term.setCursorPos(_mChoices.x, _mChoices.y)
1081 term.write(_mChoices.name)
1082 term.setCursorPos(_eChoices.x, _eChoices.y)
1083 term.write(_eChoices.name)
1084
1085 local _t = sPath or "Unnamed"
1086 if #_t > w - 14 then _t = _t:sub(1,#_t-math.max(sName,14)-3).."..."..sName end
1087 term.setTextColour(colours.grey)
1088 term.setCursorPos(w/2-#_t/2,1)
1089 term.write(_t)
1090 if sChange then
1091 term.setTextColour(colours.red)
1092 term.write("*")
1093 end
1094end
1095
1096--[[ Clears and redraws the screen ]]--
1097local function clearAndRedraw()
1098 displayClearPage()
1099 term.setBackgroundColour(colours.white)
1100 term.setTextColour(colours.black)
1101 printPage(currpage, term, _xoff + 1, _yoff + yscroll)
1102 displayInterface()
1103end
1104
1105--[[ Draws the WYSIwrite logo. ENORMOUSLY lazy but I'm over this. ]]--
1106local function drawLogo(x,y)
1107 term.setBackgroundColour(colours.grey)
1108 for i=2,3 do
1109 term.setCursorPos(x,y+i)
1110 term.write(string.rep(" ", 5))
1111 end
1112 term.setBackgroundColour(colors.white)
1113 for i=0,3 do if i ~= 2 then
1114 term.setCursorPos(x + 1, y+i)
1115 term.write(string.rep(" ",3))
1116 end end
1117 term.setCursorPos(x+2,y+1)
1118 term.write("W")
1119end
1120
1121--[[ Shows a quick "about" page for those curious ]]--
1122local function displayAbout()
1123 local _dw,_dh = w/2+3,6
1124 local _l,_t = math.floor(w/2-_dw/2),6
1125
1126 clearAndRedraw()
1127 term.setBackgroundColour(colours.lightGrey)
1128 term.setTextColour(colours.black)
1129 for _y=0,_dh do
1130 term.setCursorPos(_l,_t+_y)
1131 term.write(string.rep(" ", _dw))
1132 end
1133 term.setCursorPos(_l + 7, _t + 2)
1134 term.write("WYSIwrite")
1135 term.setCursorPos(_l + 7, _t + 4)
1136 term.write("By Nitrogen Fingers")
1137 drawLogo(_l + 1, _t + 1)
1138 while true do
1139 local id,_key = os.pullEvent()
1140 if id == "timer" and _key == bTimer then saveDocument(bPath)
1141 else break end
1142 end
1143 lscroll = true
1144end
1145
1146--[[ A recycled centre print function I've gotten a LOT of mileage out of
1147 Parameters: msg:string= the message to display
1148 height:int= the y position of the start of the message
1149 width:int= the allowed width of the message
1150 offset:int= the leftmost border of the message
1151 Returns:int= the number of lines printed
1152]]--
1153local function wprintOffCenter(msg, height, width, offset)
1154 local inc = 0
1155 local ops = 1
1156 while #msg - ops > width do
1157 local nextspace = 0
1158 while string.find(msg, " ", ops + nextspace) and
1159 string.find(msg, " ", ops + nextspace) - ops < width do
1160 nextspace = string.find(msg, " ", nextspace + ops) + 1 - ops
1161 end
1162 local ox,oy = term.getCursorPos()
1163 term.setCursorPos(width/2 - (nextspace)/2 + offset, height + inc)
1164 inc = inc + 1
1165 term.write(string.sub(msg, ops, nextspace + ops - 1))
1166 ops = ops + nextspace
1167 end
1168 term.setCursorPos(width/2 - #string.sub(msg, ops)/2 + offset, height + inc)
1169 term.write(string.sub(msg, ops))
1170
1171 return inc + 1
1172end
1173
1174--[[Produces a nice dropdown menu based on a table of strings. Depending on the position, this will auto-adjust the position
1175 of the menu drawn, and allows nesting of menus and sub menus. Clicking anywhere outside the menu will cancel and return nothing
1176 Params: x:int = the x position the menu should be displayed at
1177 y:int = the y position the menu should be displayed at
1178 options:table = the list of options available to the user, as strings or submenus (tables of strings, with a name parameter)
1179 Returns:string the selected menu option.
1180]]--
1181local function displayDropDown(x, y, options, noTitle)
1182 inDropDown = true
1183 if noTitle then y = y - 1 end
1184 --Figures out the dimensions of our thing
1185 local longestX = #options.name
1186 for i=1,#options do
1187 local currVal = options[i]
1188 if type(currVal) == "table" then currVal = currVal.name end
1189 longestX = math.max(longestX, #currVal)
1190 end
1191 local xOffset = math.max(0, longestX - ((w-2) - x) + 1)
1192 local yOffset = math.max(0, #options - ((h-1) - y))
1193
1194 local clickTimes = 0
1195 local tid = nil
1196 local selection = nil
1197 while clickTimes < 4 do
1198 if not noTitle then
1199 term.setCursorPos(x-xOffset,y-yOffset)
1200 term.setBackgroundColour(colours.blue)
1201 term.setTextColour(colours.white)
1202 term.write(options.name.." ")
1203 end
1204
1205 for i=1,#options do
1206 term.setCursorPos(x-xOffset, y-yOffset+i)
1207 local currVal = options[i]
1208 if type(currVal.value) == "table" then
1209 currVal.enabled = #currVal.value > 0
1210 end
1211
1212 if i==selection and clickTimes % 2 == 0 then
1213 term.setBackgroundColour(colours.blue)
1214 if options[selection].enabled then term.setTextColour(colours.white)
1215 else term.setTextColour(colours.grey) end
1216 else
1217 term.setBackgroundColour(colours.lightGrey)
1218 local _tcol = colours.black
1219 if not currVal.enabled then _tcol = colours.grey end
1220 term.setTextColour(_tcol)
1221 end
1222 if type(currVal.value) == "table" then
1223 term.write(currVal.name..string.rep(" ", longestX-#currVal.name))
1224 term.setBackgroundColour(colours.blue)
1225 term.setTextColour(colours.white)
1226 term.write(">")
1227 else
1228 term.write(currVal.name..string.rep(" ", longestX-#currVal.name + 1))
1229 end
1230
1231 if (i~= selection or clickTimes %2 == 1) and currVal.key and currVal.enabled then
1232 term.setTextColour(colours.blue)
1233 term.setBackgroundColour(colours.lightGrey)
1234 local co = currVal.name:find(currVal.key)
1235 if not co then co = longestX else co = co - 1 end
1236 term.setCursorPos(x-xOffset+co, y-yOffset + i)
1237 term.write(currVal.key)
1238 end
1239 end
1240
1241 local id, p1, p2, p3 = os.pullEvent()
1242 if id == "timer" then
1243 if p1 == tid then
1244 clickTimes = clickTimes + 1
1245 if clickTimes > 2 then
1246 break
1247 else
1248 tid = os.startTimer(0.1)
1249 end
1250 elseif p1 == bTimer then saveDocument(bPath) end
1251 elseif id == "key" and not tid then
1252 if p1 == keys.leftCtrl then
1253 selection = ""
1254 break
1255 elseif p1 == keys.down and (not selection or selection < #options) then
1256 selection = selection or 0
1257 _os = selection
1258 repeat selection = selection + 1
1259 until selection == #options + 1 or options[selection].enabled
1260 --if selection == #options + 1 then selection = _os end
1261 elseif p1 == keys.up and (not selection or selection > 1) then
1262 selection = selection or #options + 1
1263 _os = selection
1264 repeat selection = selection - 1
1265 until selection == 0 or options[selection].enabled
1266 if selection == 0 then selection = _os end
1267 elseif p1 == keys.enter and selection and options[selection].enabled then
1268 tid = os.startTimer(0.1)
1269 clickTimes = clickTimes - 1
1270 end
1271 elseif id == "mouse_click" and not tid then
1272 local _xp, _yp = x - xOffset, y - yOffset
1273 if p2 >= _xp and p2 <= _xp + longestX + 1 and
1274 p3 >= _yp+1 and p3 <= _yp+#options then
1275 if options[p3 - _yp].enabled then
1276 selection = p3-(_yp)
1277 tid = os.startTimer(0.1)
1278 end
1279 else
1280 selection = ""
1281 break
1282 end
1283 elseif id == "char" and not tid then
1284 for k,v in ipairs(options) do
1285 if v.key and v.key:lower() == p1:lower() and options[k].enabled then
1286 selection = k
1287 tid = os.startTimer(0.1)
1288 break
1289 end
1290 end
1291 end
1292 end
1293
1294 local _val = selection
1295 if type(selection) == "number" then
1296 selection = options[selection].value
1297 end
1298
1299 if type(selection) == "string" then
1300 inDropDown = false
1301 return selection
1302 elseif type(selection) == "table" then
1303 return displayDropDown(x + longestX + 1, y + _val, selection, true)
1304 elseif type(selection) == "function" then
1305 local _rval = selection()
1306 if not _rval then return "" else return _rval end
1307 end
1308end--[[ Copied out of other menus, simply provides a display to indicate something has happened.
1309 Parameters: ctitle:string = the title of the dialogue
1310 msg:string = the message in the dialogue
1311 arg:{string, colour} = pairs of buttons with strings and their text colour (background is default)
1312 Returns: none
1313]]--
1314local function displayConfirmDialogue(ctitle, msg, ...)
1315 local _doffX, _doffY = 8, 5
1316 --We actually print twice- once to get the lines, second time to print proper. Easier this way.
1317 local lines = wprintOffCenter(msg, _doffY, w - (_doffX+2) * 2, _doffX + 2)
1318
1319 term.setCursorPos(_doffX, 3)
1320 term.setBackgroundColour(colours.blue)
1321 term.setTextColour(colours.white)
1322 term.write(string.rep(" ", w - _doffX * 2))
1323 term.setCursorPos(_doffX + (w - _doffX * 2)/2 - #ctitle/2, 3)
1324 term.write(ctitle)
1325 term.setTextColour(colours.black)
1326 term.setBackgroundColour(colours.lightGrey)
1327 term.setCursorPos(_doffX, 4)
1328 term.write(string.rep(" ", w - _doffX * 2))
1329 for i = _doffY, _doffY + lines do
1330 term.setCursorPos(_doffX, i)
1331 term.write(" "..string.rep(" ", w - (_doffX) * 2 - 2).." ")
1332 end
1333 wprintOffCenter(msg, _doffY, w - (_doffX+2) * 2, _doffX + 2)
1334
1335 if arg then
1336 term.setCursorPos(_doffX, _doffY + lines + 1)
1337 term.write(string.rep(" ", w - _doffX * 2))
1338 term.setCursorPos(_doffX, _doffY + lines + 2)
1339 term.write(string.rep(" ", w - _doffX * 2))
1340
1341 local _sspace = 0
1342 for _k,_v in ipairs(arg) do _sspace = _sspace + #_v[1] end
1343 _sspace = (w - (_doffX * 2) - _sspace - (2 * #arg))/(#arg)
1344 if _sspace <= #arg - 1 then assert(false, "Too little space: needed "..(#arg - 1)..", got ".._sspace) end
1345 term.setBackgroundColour(colours.grey)
1346 term.setCursorPos(_doffX + 1, _doffY + lines + 1)
1347 local _spart = false
1348 for i=1,#arg do
1349 _v = arg[i]
1350 arg[i][3] = term.getCursorPos()
1351 term.setTextColour(_v[2])
1352 term.write(" ".._v[1].." ")
1353 local _vspace = math.floor(_sspace)
1354 if i >= #arg/2 and not _spart then _vspace = math.ceil(_sspace) _spart = true end
1355 local _x,_y = term.getCursorPos()
1356 term.setCursorPos(_x + _vspace, _y)
1357 end
1358 end
1359
1360 --In the event of a message, the player hits anything to continue
1361 while true do
1362 local _id,_p1,_p2,_p3 = os.pullEvent()
1363 if (not arg or #arg == 0) and (id == "key" or id == "mouse_click" or id == "mouse_drag") then
1364 break
1365 elseif _id == "key" then
1366 if _p1 == keys.enter then return 1 end
1367 if _p1 == keys.backspace then return 2 end
1368 elseif _id == "mouse_click" and _p3 == _doffY + lines + 1 then
1369 for i=1,#arg do
1370 _v = arg[i]
1371 if _p2 >= _v[3] and _p2 <= _v[3] + #_v[1] + 1 then return i end
1372 end
1373 elseif _id == "timer" and _p1 == bTimer then saveDocument(bPath) end
1374 end
1375end
1376
1377--[[ Runs a printing interface that lets users select the pages and number of copies to print
1378 Also displays toner and paper levels, and returns the start, end and copy number, or null.
1379]]--
1380local function displayPrint(_printer,_w,_h,_y,_x)
1381 _w = math.floor(_w)
1382 if not _x then _x = math.floor(w/2-_w/2) else _x = math.floor(_x) end
1383 if not _y then _y = math.floor(h/2-_h/2) end
1384 local _bc,_tc = colours.lightGrey, colours.black
1385 local _btc,_ttc = colours.blue, colours.white
1386 local _bfc,_tfc,_tdc = colours.black, colours.white, colours.green
1387 --The position of the text boxes (which can vary)
1388 local _cXoff,_cYoff = 0,0
1389 local _pX,_pY,_cX,_cY = 0,0,0,0
1390 --The selected pages and copies
1391 local _pstart, _pend, _copies = 1, #doc, 1
1392 --The amount of paper and ink required
1393 local _pageCount = (_pend - _pstart + 1) * _copies
1394 local _ilX,_ilY = 0,0
1395
1396 local _wsel = 0
1397
1398 local function _drawWindow()
1399 --Window title
1400 term.setBackgroundColour(_btc)
1401 term.setTextColour(_ttc)
1402 term.setCursorPos(_x,_y)
1403 _title = "Print"
1404 term.write(string.rep(" ", math.floor(_w/2) - math.floor(#_title/2))
1405 .._title..string.rep(" ", math.ceil(_w/2) - math.ceil(#_title/2)-1))
1406 term.setBackgroundColour(colours.red)
1407 term.setTextColour(colours.white)
1408 term.write("x")
1409 --Body and main options
1410 term.setBackgroundColour(_bc)
1411 term.setTextColour(_tc)
1412 for i=1,_h do
1413 term.setCursorPos(_x,_y+i)
1414 term.write(string.rep(" ", _w))
1415 end
1416
1417 --Hardcoded value. To change.
1418 local _plen = math.log10(#doc) + 1
1419 term.setCursorPos(_x + 1, _y + 2)
1420 term.write ("Print: ")
1421 _pX,_pY = term.getCursorPos()
1422 term.setBackgroundColour(colours.white)
1423 term.write(string.rep(" ", _plen))
1424 term.setBackgroundColour(_bc)
1425 term.write("-")
1426 term.setBackgroundColour(colours.white)
1427 term.write(string.rep(" ", _plen))
1428 term.setBackgroundColour(_bc)
1429
1430 local _ctoken = "Copies: "
1431 local _clen,_cx,_cy = 2, term.getCursorPos()
1432 if _cx + #_ctoken + _clen + 2 > _x + _w - 1 then
1433 _cXoff = 1
1434 _cYoff = 2
1435 _ilY = _cy + 4
1436 term.setCursorPos(_x + _cXoff, _cy + _cYoff)
1437 else
1438 _cXoff = _cx + #_ctoken + 3
1439 _ilY = _cy + 2
1440 term.write(" ")
1441 end
1442 term.write(_ctoken)
1443 _cX, _cY = term.getCursorPos()
1444 term.setBackgroundColour(colours.white)
1445 term.write(" ")
1446
1447 term.setBackgroundColour(_bc)
1448 term.setCursorPos(_x + 1, _ilY)
1449 term.write("Toner level: ")
1450 term.setCursorPos(_x + 1, _ilY + 1)
1451 term.write("Paper level: ")
1452 _ilX = term.getCursorPos()
1453
1454 --Cancel button
1455 term.setBackgroundColour(colours.grey)
1456 term.setTextColour(colours.red)
1457 term.setCursorPos(_x + _w - 10, _y + _h - 1)
1458 term.write(" Cancel ")
1459 end
1460
1461 local function _drawPrintOptions()
1462 --Numbers in the pages and copies box
1463 if _wsel ~= 1 and _pstart == nil then _pstart = 1 end
1464 if _wsel ~= 2 and _pend == nil then _pend = 1 end
1465 if _wsel ~= 3 and _copies == nil then _copies = 1 end
1466
1467 term.setCursorPos(_pX, _pY)
1468 term.setTextColour(colours.black)
1469 if _wsel == 1 then term.setBackgroundColour(colours.orange)
1470 else term.setBackgroundColour(colours.white) end
1471 if _pstart == nil then term.write(string.rep(" ", math.log10(#doc) + 1))
1472 else term.write(string.rep(" ", math.floor(math.log10(#doc)) - math.floor(math.log10(_pstart))).._pstart) end
1473 if _wsel == 2 then term.setBackgroundColour(colours.orange)
1474 else term.setBackgroundColour(colours.white) end
1475 term.setCursorPos(term.getCursorPos() + 1, _pY)
1476 if _pend == nil then term.write(string.rep(" ", math.log10(#doc) + 1))
1477 else term.write(string.rep(" ", math.floor(math.log10(#doc)) - math.floor(math.log10(_pend))).._pend) end
1478 if _wsel == 3 then term.setBackgroundColour(colours.orange)
1479 else term.setBackgroundColour(colours.white) end
1480 term.setCursorPos(_cX, _cY)
1481 if _copies == nil then term.write(string.rep(" ", 2))
1482 else term.write(string.rep(" ", 1 - math.floor(math.log10(_copies))).._copies) end
1483
1484 --Ink and paper levels
1485 local _iLvl, _pLvl = _printer.getInkLevel(), _printer.getPaperLevel()
1486 if not _pstart or not _pend or not _copies then _pageCount = -1
1487 else _pageCount = (_pend - _pstart + 1) * _copies end
1488 if _pageCount <= 0 then _pageCount = -1 end
1489 term.setBackgroundColour(_bc)
1490 if _iLvl < _pageCount then term.setTextColour(colours.red)
1491 else term.setTextColour(colours.black) end
1492 term.setCursorPos(_ilX, _ilY)
1493 term.write(_iLvl.."")
1494 term.setTextColour(colours.black)
1495 if _pageCount == -1 then term.write(" (--)")
1496 else term.write(" (".._pageCount..")") end
1497 term.write(string.rep(" ", _x + _w - term.getCursorPos()))
1498 term.setCursorPos(_ilX, _ilY + 1)
1499 if _pLvl < _pageCount then term.setTextColour(colours.red)
1500 else term.setTextColour(colours.black) end
1501 term.write(_pLvl.."")
1502
1503 --Print/Invalid button
1504 term.setCursorPos(_x + 1, _y + _h - 1)
1505 term.setBackgroundColour(colours.grey)
1506 if not _pstart or not _pend or _pstart > _pend then
1507 term.setTextColour(colours.lightGrey)
1508 term.write(" Invalid ")
1509 else
1510 term.setTextColour(colours.green)
1511 term.write(" Print ")
1512 term.setBackgroundColour(colours.lightGrey)
1513 term.write(" ")
1514 end
1515 end
1516
1517 local function _runInput()
1518 while true do
1519 local _id, _p1, _p2, _p3 = os.pullEvent()
1520 if _id == "mouse_click" then
1521 if _p2 >= _pX and _p2 <= _pX + math.floor(math.log10(#doc)) and _p3 == _pY then
1522 _wsel = 1
1523 _pstart = nil
1524 _drawPrintOptions()
1525 elseif _p2 >= _pX + math.floor(math.log10(#doc)) + 2 and _p2 <= _pX + (2*math.floor(math.log10(#doc))) + 2 and
1526 _p3 == _pY then
1527 _pend = nil
1528 _wsel = 2
1529 _drawPrintOptions()
1530 elseif _p2 >= _cX and _p2 <= _cX + 1 and _p3 == _cY then
1531 _copies = nil
1532 _wsel = 3
1533 _drawPrintOptions()
1534 elseif _p2 >= _x + 1 and _p2 <= _x + 8 and _p3 == _y + _h - 1 and _pstart <= _pend then
1535 if _pstart == nil then _pstart = 1 end
1536 if _pend == nil then _pend = #doc end
1537 if _copies == nil then _copies = 1 end
1538 return true
1539 elseif (_p2 >= _x + _w - 9 and _p2 <= _x + _w - 1 and _p3 == _y + _h - 1) or
1540 (_p2 == _x + _w - 1 and _p3 == _y) then
1541 return false
1542 else
1543 _wsel = 0
1544 _drawPrintOptions()
1545 end
1546 elseif _id == "key" then
1547 if _p1 == keys.delete or _p1 == keys.backspace then
1548 if _wsel == 1 and _pstart then _pstart = math.floor(_pstart / 10) if _pstart == 0 then _pstart = nil end
1549 elseif _wsel == 2 and _pend then _pend = math.floor(_pend / 10) if _pend == 0 then _pend = nil end
1550 elseif _wsel == 3 and _copies then _copies = math.floor(_copies / 10) if _copies == 0 then _copies = nil end
1551 end
1552 _drawPrintOptions()
1553 elseif _p1 == keys.tab then
1554 _wsel = (_wsel % 3) + 1
1555 _drawPrintOptions()
1556 elseif _p1 == keys.enter and _pstart <= _pend then
1557 if _pstart == nil then _pstart = 1 end
1558 if _pend == nil then _pend = #doc end
1559 if _copies == nil then _copies = 1 end
1560 return true
1561 end
1562 elseif _id == "char" and _wsel > 0 and tonumber(_p1) then
1563 if _wsel == 1 then
1564 if not _pstart then _pstart = 0 end
1565 _pstart = (10 * _pstart) + tonumber(_p1)
1566 if _pstart > #doc then _pstart = #doc
1567 elseif _pstart == 0 then _pstart = nil end
1568 _drawPrintOptions()
1569 elseif _wsel == 2 then
1570 if not _pend then _pend = 0 end
1571 _pend = (10 * _pend) + tonumber(_p1)
1572 if _pend > #doc then _pend = #doc
1573 elseif _pend == 0 then _pend = nil end
1574 _drawPrintOptions()
1575 elseif _wsel == 3 then
1576 if not _copies then _copies = 0 end
1577 _copies = (10 * _copies) + tonumber(_p1)
1578 if _copies > 99 then _copies = 99
1579 elseif _copies == 0 then _copies = nil end
1580 _drawPrintOptions()
1581 end
1582 elseif _id == "timer" and _p1 == bTimer then saveDocument(bPath) end
1583 end
1584 end
1585
1586 _drawWindow()
1587 _drawPrintOptions()
1588 if _runInput() then return _pstart, _pend, _copies end
1589end
1590
1591--[[ A more elaborate save menu than my first effort. Directory navigation and
1592 selection. It works well enough.
1593]]--
1594local function displayFileBrowser(_flag,_h,_w,_y,_x)
1595 local _mt = { ["-s"] = "Save As", ["-b"] = "Browse Files", ["-l"] = "Load File" }
1596 if not _h then _h = math.floor(h/2) else _h = math.floor(_h) end
1597 if not _w then _w = math.floor(w/2) else _w = math.floor(_w) end
1598 if not _x then _x = math.floor(w/2-_w/2) + 1 else _x = math.floor(_x) end
1599 if not _y then _y = math.floor(h/2-_h/2) end
1600 local _bc,_tc = colours.lightGrey, colours.black
1601 local _btc,_ttc = colours.blue, colours.white
1602 local _bfc,_tfc,_tdc = colours.black, colours.white, colours.green
1603 local _fname = sName or ""
1604 --This is a nasty timesaver.
1605 local _cpath = sPath or "/"..shell.resolve(".")
1606 if not _cpath:find("/") then
1607 _cpath = ""
1608 elseif sPath then
1609 _cpath = "/"..getPreviousDir(_cpath)
1610 end
1611 local _rlist = fs.list(_cpath)
1612 if _cpath ~= "/" and _cpath ~= "" then table.insert(_rlist, 1, "..") end
1613
1614 local _scr = 0
1615 local _abmsg = { ["-l"] = " Open ", ["-s"] = " Save As ",
1616 [1] = " Invalid ", [2] = " Overwrite " }
1617 local _labmsg = ""
1618 local _winh = _h - 7
1619 local _cpos = 0
1620
1621 clearAndRedraw()
1622 --Some dedicated internal draw functions (to speed things up)
1623 local function _drawWindow()
1624 --Permanent parts of the window
1625 term.setBackgroundColour(_btc)
1626 term.setTextColour(_ttc)
1627 term.setCursorPos(_x,_y)
1628 term.write(string.rep(" ", math.floor(_w/2) - math.floor(#_mt[_flag]/2))
1629 .._mt[_flag]..string.rep(" ", math.ceil(_w/2) -
1630 math.ceil(#_mt[_flag]/2)-1))
1631 term.setBackgroundColour(colours.red)
1632 term.setTextColour(colours.white)
1633 term.write("x")
1634 term.setBackgroundColour(_bc)
1635 term.setTextColour(_tc)
1636 for i=1,_h do
1637 term.setCursorPos(_x,_y+i)
1638 term.write(string.rep(" ", _w))
1639 end
1640
1641 term.setBackgroundColour(colours.grey)
1642 term.setTextColour(colours.red)
1643 term.setCursorPos(_x + _w - 10, _y + _h - 1)
1644 term.write(" Cancel ")
1645 end
1646
1647 local function _drawBrowser()
1648 term.setBackgroundColour(_bc)
1649 term.setTextColour(_tc)
1650 local _dpath = _cpath
1651 if #_dpath > _w-4 then
1652 while _dpath:find("/") do
1653 local _ind = _dpath:find("/") + 1
1654 _dpath = _dpath:sub(_ind)
1655 if #_dpath < _w-7 then
1656 _dpath = "...".._dpath
1657 break
1658 end
1659 end
1660 end
1661 if #_dpath > _w-4 then _dpath = "...".._dpath:sub(_w-4-#_dpath) end
1662 term.setCursorPos(_x+2,_y+2)
1663 term.write(_dpath..string.rep(" ", _w-4-#_dpath))
1664
1665 term.setBackgroundColour(_bfc)
1666 for i = 1 + _scr, _winh + _scr do
1667 _pth = _rlist[i] or ""
1668 term.setCursorPos(_x + 2, _y + 2 + i - _scr)
1669 if fs.isDir(_cpath.."/".._pth) then
1670 term.setTextColour(_tdc)
1671 else term.setTextColour(_tfc) end
1672 term.write(" ".._pth..string.rep(" ", _w - 5 - #_pth))
1673 end
1674 end
1675
1676 local function _drawActivationButton()
1677 _owrite = 0
1678 local _val = _cpath.."/".._fname
1679 if (not fs.exists(_val) and _flag == "-l") or
1680 (fs.exists(_val) and fs.isDir(_val)) then
1681 _owrite = 1
1682 elseif fs.exists(_val) and _flag == "-s" then _owrite = 2 end
1683 term.setBackgroundColour(colours.grey)
1684 term.setCursorPos(_x + 2, _y + _h - 1)
1685 if _owrite == 1 and _flag ~= "-b" then
1686 term.setTextColour(colours.lightGrey)
1687 _labmsg = " Invalid "
1688 elseif _owrite == 2 then
1689 term.setTextColour(colours.orange)
1690 _labmsg = " Overwrite "
1691 elseif _flag == "-s" then
1692 term.setTextColour(colours.green)
1693 _labmsg = " Save "
1694 elseif _flag == "-l" then
1695 term.setTextColour(colours.green)
1696 _labmsg = " Open "
1697 end
1698 term.write(_labmsg)
1699 term.setBackgroundColour(_bc)
1700 term.write(string.rep(" ", #" Overwrite " - #_labmsg))
1701 end
1702
1703 local function _drawWriteBar()
1704 term.setCursorPos(_x + 2, _y + _h - 3)
1705 term.setTextColour(colours.black)
1706 term.setBackgroundColour(colours.lightGrey)
1707 local _pm = "Save As "
1708 if _flag == "-l" then _pm = "Open " end
1709 term.write(_pm)
1710
1711 local _msg = _fname
1712 if #_msg > _w - 8 - #_pm then _msg = _msg:sub(#_msg - (_w - 8 - #_pm)) end
1713 term.setBackgroundColour(colours.white)
1714 term.write(" ".._msg)
1715 _cpos = term.getCursorPos()
1716 term.write(string.rep(" ", math.max(_w - 5 - #_pm - #_msg, 1)))
1717 end
1718
1719 _drawWindow()
1720 _drawActivationButton()
1721 _drawBrowser()
1722 _drawWriteBar()
1723
1724 while true do
1725 term.setTextColour(colours.black)
1726 term.setCursorPos(_cpos, _y + _h - 3)
1727 term.setCursorBlink(true)
1728 local _id,_p1,_p2,_p3 = os.pullEvent()
1729
1730 if _id == "key" then
1731 if _p1 == keys.backspace and #_fname > 0 then
1732 _fname = _fname:sub(1,#_fname-1)
1733 _drawActivationButton()
1734 _drawWriteBar()
1735 elseif _p1 == keys.up and _scr > 0 then
1736 _scr = _scr - 1
1737 _drawBrowser()
1738 elseif _p1 == keys.down and _scr < #_rlist - _winh then
1739 _scr = _scr + 1
1740 _drawBrowser()
1741 elseif _p1 == keys.enter then
1742 local _val = _cpath.."/".._fname
1743 if (_flag == "-l" and fs.exists(_val) and not fs.isDir(_val))
1744 or(_flag == "-s" and not fs.isDir(_val)) then
1745 break
1746 end
1747 elseif _p1 == keys.leftCtrl or _p == keys.rightCtrl then
1748 _fname = nil
1749 break
1750 end
1751 elseif _id == "char" then
1752 _fname = _fname.._p1
1753 _drawActivationButton()
1754 _drawWriteBar()
1755 elseif _id == "mouse_click" then
1756 if _p2 == _x + _w - 1 and _p3 == _y then
1757 _fname = nil
1758 break
1759 elseif _p2 >= _x + 2 and _p2 <= _x + _w - 2 and _p3 >= _y + 3 and
1760 _p3 < _y + 3 + _winh then
1761 _p3 = _p3 - _y - 2
1762 local _val = _rlist[_p3 + _scr]
1763 if _val == ".." then
1764 --Wow why is there no reverse find
1765 _cpath = getPreviousDir(_cpath)
1766 _rlist = fs.list(_cpath)
1767 if _cpath ~= "/" and _cpath ~= "" then table.insert(_rlist, 1, "..") end
1768 _scr = 0
1769 _drawBrowser()
1770 _drawActivationButton()
1771 elseif fs.isDir(_cpath.."/".._val) then
1772 _cpath = _cpath.."/".._val
1773 _rlist = fs.list(_cpath)
1774 if _cpath ~= "/" and _cpath ~= "" then table.insert(_rlist, 1, "..") end
1775 _scr = 0
1776 _drawBrowser()
1777 _drawActivationButton()
1778 else
1779 _fname = _val or _fname
1780 _drawActivationButton()
1781 _drawWriteBar()
1782 end
1783 elseif _p3 == _y + _h - 1 and _p2 >= _x + 2 and _p2 < _x + 2 + # _labmsg and _labmsg ~= _abmsg[1] then
1784 break
1785 elseif _p3 == _y + _h - 1 and _p2 >= _x + _w - 2 - #" Cancel " and
1786 _p2 < _x + _w - 2 then
1787 _fname = nil
1788 return false
1789 end
1790 elseif _id == "mouse_scroll" then
1791 _scr = math.min(_scr, #_rlist - _winh - 1)
1792 _scr = math.max(_scr + _p1, 0)
1793 _drawBrowser()
1794 elseif _id == "timer" and _p1 == bTimer then saveDocument(bPath) end
1795 end
1796 if _fname then return (_cpath.."/".._fname):sub(2),_fname else return nil end
1797end
1798
1799local function menuSaveAs()
1800 local _sp,sn = displayFileBrowser("-s", 14, 24, 3)
1801 if _sp then
1802 sPath = _sp
1803 sName = _sn
1804 saveDocument()
1805 end
1806end
1807
1808local function checkSave()
1809 if not sChange then return true end
1810 clearAndRedraw()
1811 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";
1812 [2] = colors.red; })
1813 if _val == 1 then
1814 if sPath then saveDocument()
1815 else menuSaveAs() end
1816 return true
1817 elseif _val == 2 then
1818 return true
1819 else
1820 return false
1821 end
1822end
1823
1824--[[ Updates the list of currently available printers and other menu options
1825 Params: none
1826 Returns: none
1827]]--
1828local function updateMenu()
1829 --Again, horrible. My coding sucks.
1830 for i=1,6 do _printers[i] = nil end
1831 for _,_s in pairs(peripheral.getNames()) do
1832 if peripheral.getType(_s) == "printer" then
1833 table.insert(_printers, { name = _s, enabled = true, value = _s })
1834 end
1835 end
1836 _mChoices[3].enabled = sChange and sPath
1837 _mChoices[4].enabled = sChange and sPath
1838 _eChoices[1].enabled = #undolist > 0
1839 _eChoices[2].enabled = #redolist > 0
1840end
1841
1842--Performs a print with a given printer
1843local function performPrint(_val)
1844 _p = peripheral.wrap(_val)
1845 clearAndRedraw()
1846 local _ps,_pe,_cp = displayPrint(_p, math.ceil(w/2), 8)
1847 if not _ps then
1848 lscroll = true
1849 return
1850 end
1851 clearAndRedraw()
1852 for c=1,_cp do
1853 for i=_ps,_pe do
1854 while _p.getPaperLevel() == 0 do
1855 displayConfirmDialogue("Page "..i..": Check paper levels", "The paper tray is currently empty. Fill with paper and click here to continue.")
1856 end
1857 while _p.getInkLevel() == 0 do
1858 displayConfirmDialogue("Page "..i..": Check ink levels", "The printer is out of ink. Insert more ink, and click here to continue.")
1859 end
1860 --There is no out-tray check... that I can tell at least.
1861 while not _p:newPage() do
1862 displayConfirmDialogue("Page "..i..": Check out tray", "The out tray may be full. Remove all pages and click here to continue.")
1863 end
1864 _p.setPageTitle((sName or "").." page "..i)
1865 printPage(i, _p)
1866 _p:endPage()
1867 end
1868 if c<_cp then displayConfirmDialogue("Copy "..c.." Complete!", "Clear out tray and click here to continue") end
1869 end
1870
1871 clearAndRedraw()
1872 displayConfirmDialogue("Print Complete!", "Printed ".._cp.." copies (total "..#doc.." page(s).)")
1873end
1874
1875--Performs the menu
1876local function performMenu(_menu)
1877 updateMenu()
1878 local _val = displayDropDown(_menu.x, _menu.y, _menu)
1879
1880 if peripheral.getType(_val) == "printer" then performPrint(_val) end
1881 lscroll = true
1882end
1883
1884--[[ A series of functions binded to keyboard shortcuts
1885 Parameters: none
1886 Returns: none
1887]]--
1888local bindings = {
1889 --Moves us up. Hitting the top moves the cursor to the start of the line.
1890 --Most editors do this. I don't know why. Convention?
1891 [keys.up] = function()
1892 if currline > 1 then currline = currline - 1
1893 elseif currpage > 1 then
1894 currpage = currpage - 1
1895 currline = #doc[currpage]
1896 lscroll = true
1897 else
1898 currind = 1
1899 return
1900 end
1901 if currind > #doc[currpage][currline] then
1902 currind = math.max(1, #doc[currpage][currline] + 1)
1903 end
1904 fixCursorPos()
1905 checkYScroll()
1906 end,
1907 --Moves us down. Hitting the bottom moves cursor to end of the line.
1908 [keys.down] = function()
1909 if currline < #doc[currpage] then currline = currline + 1
1910 elseif currpage < #doc then
1911 currpage = currpage + 1
1912 currline = 1
1913 lscroll = true
1914 else
1915 currind = #doc[currpage][currline] + 1
1916 return
1917 end
1918 if currind > #doc[currpage][currline] then
1919 currind = math.max(1, #doc[currpage][currline] + 1)
1920 end
1921 fixCursorPos()
1922 checkYScroll()
1923 end,
1924 --Moves us left. Hitting and edge moves to the end of the last line.
1925 [keys.left] = function()
1926 if currind <= 1 then
1927 if currline > 1 then
1928 currline = currline - 1
1929 currind = #doc[currpage][currline] + 1
1930 elseif currpage > 1 then
1931 currpage = currpage - 1
1932 currline = #doc[currpage]
1933 currind = #doc[currpage][currline] + 1
1934 end
1935 else
1936 currind = currind - 1
1937 end
1938 fixCursorPos()
1939 checkYScroll()
1940 end,
1941 --Moves us right. Hitting the edge moves to the start of the next line.
1942 [keys.right] = function()
1943 local _c = doc[currpage][currline]:sub(currind)
1944 if currind >= #doc[currpage][currline] + 1 or _c == "\n" or _c == "\f" then
1945 if currline < #doc[currpage] then
1946 currline = currline + 1
1947 currind = 1
1948 elseif currpage < #doc then
1949 currpage = currpage + 1
1950 currline = 1
1951 currind = 1
1952 end
1953 else
1954 currind = currind + 1
1955 end
1956 fixCursorPos()
1957 checkYScroll()
1958 end,
1959 --[[ Adds a newline character to the end of the line, and pushes everything down ]]
1960 [keys.enter] = function()
1961 addUndoableAction(currind, ACTIONS.ADDBREAK, "\n")
1962 insertLineBreak("\n")
1963 end,
1964 --Some more simple cursor manipulation keys
1965 [keys.home] = function() currind = 1 end,
1966 [keys["end"]] = function()
1967 currind = #doc[currpage][currline] + 1
1968 fixCursorPos()
1969 end,
1970 [keys.pageDown] = function()
1971 if currpage < #doc then
1972 currpage = currpage + 1
1973 currline = 1 currind = 1
1974 checkYScroll()
1975 end
1976 lscroll = true
1977 end,
1978 [keys.pageUp] = function()
1979 if currpage > 1 then
1980 currpage = currpage - 1
1981 currline = 1 currind = 1
1982 checkYScroll()
1983 end
1984 lscroll = true
1985 end,
1986 -- Some other core functions in the program
1987 [keys.backspace] = function ()
1988 local _val = deleteCharacter()
1989 addUndoableAction(currind, ACTIONS.DELETECHAR, _val)
1990 end;
1991 [keys.leftCtrl] = function() performMenu(_mChoices) end,
1992 [keys.grave] = function() testMode = not testMode end,
1993 [keys.tab] = function()
1994 local _len = #doc[currpage][currline]
1995 local _add = math.min(4 - (_len % 4), linelen - currind + 1)
1996 for i=1,_add do addCharacter(" ") end
1997 end
1998}
1999
2000
2001--[[ Performs actions based on keyboard and mouse input
2002 Parameters: none
2003 Returns: none
2004]]--
2005local function handleInput()
2006 local _,_cpy = term.getCursorPos()
2007 if _cpy > 1 then term.setCursorBlink(true) end
2008 local _id,_p1,_p2,_p3 = os.pullEvent()
2009 term.setCursorBlink(false)
2010 local _oUndoLen,_oRedoLen = #undolist,#redolist
2011 if _id == "char" then
2012 addCharacter(_p1)
2013 addUndoableAction(currind - 1, ACTIONS.ADDCHAR, _p1)
2014 elseif _id == "key" then
2015 if bindings[_p1] then bindings[_p1]()
2016 elseif _p1 == keys.leftCtrl then finished = true end
2017 elseif _id == "mouse_scroll" then
2018 yscroll = yscroll - _p1
2019 if yscroll > 4 - _yoff then
2020 if currpage > 1 then
2021 yscroll = h - pagelen - _yoff - 3
2022 currpage = currpage - 1
2023 currline = 1
2024 currind = 1
2025 else yscroll = 4 - _yoff end
2026 elseif yscroll < h - pagelen - _yoff - 3 then
2027 if currpage < #doc then
2028 yscroll = 4 - _yoff
2029 currpage = currpage + 1
2030 currline = 1
2031 currind = 1
2032 else yscroll = h - pagelen - _yoff - 3 end
2033 end
2034 lscroll = true
2035 elseif _id == "mouse_click" then
2036 if _p2 >= _mChoices.x and _p2 <= _mChoices.x + #_mChoices.name
2037 and _p3 == _mChoices.y then
2038 performMenu(_mChoices)
2039 elseif _p2 >= _eChoices.x and _p2 <= _eChoices.x + #_eChoices.name
2040 and _p3 == _eChoices.y then
2041 performMenu(_eChoices)
2042 else
2043 currline = math.max(_p3 - _yoff - yscroll, 1)
2044 currline = math.min(currline, #doc[currpage])
2045 local _ws = getWhitespaceLength(currpage, currline)
2046 currind = math.max(_p2 - _xoff - _ws, 1)
2047 currind = math.min(currind, #doc[currpage][currline] + 1)
2048 if currind > #doc[currpage][currline] and hasBreakEnd(doc[currpage][currline])
2049 then currind = currind - 1 end
2050 checkYScroll()
2051 end
2052 elseif _id == "timer" then
2053 if _p1 == bTimer then
2054 saveDocument(bPath)
2055 elseif _p1 == actionTimer then
2056 actionTimer = nil
2057 end
2058 end
2059 --Clearing the redo list
2060 if _oUndoLen < #undolist and _oRedoLen == #redolist then redolist = {} end
2061 testWait()
2062end
2063
2064--Menu initialization
2065table.insert(_aChoices, { name = "Left", key = "L", enabled = true, value = function()
2066 testAlign = ALIGNLEFT end})
2067table.insert(_aChoices, { name = "Center", key = "C", enabled = true, value = function()
2068 testAlign = ALIGNCENTER end})
2069table.insert(_aChoices, { name = "Right", key = "R", enabled = true, value = function()
2070 testAlign = ALIGNRIGHT end})
2071table.insert(_iChoices, { name = "Page Break", key = "P", enabled = true, value = function()
2072 addUndoableAction(currind, ACTIONS.ADDBREAK, "\f")
2073 insertLineBreak("\f")
2074 end })
2075table.insert(_iChoices, { name = "Section Break", key = "S", enabled = false,
2076 value = function() testPrint("unimplemented") end })
2077table.insert(_mChoices, { name = "New File", key = "N", enabled = true, value = function()
2078 if checkSave() then constructNewDocument() end end })
2079table.insert(_mChoices, { name = "Open", key = "O", enabled = true, value = function()
2080 if checkSave() then
2081 local _sp,_sn = displayFileBrowser("-l", 14, 24, 3)
2082 if _sp then
2083 loadDocument(_sp)
2084 sPath = _sp
2085 sName = _sn
2086 end end end })
2087table.insert(_mChoices, { name = "Revert", key = "R", enabled = false, value = function()
2088 loadDocument(sPath)
2089end })
2090table.insert(_mChoices, { name = "Save", key = "S", enabled = false, value = saveDocument })
2091table.insert(_mChoices, { name = "Save As", key = "A", enabled = true, value = menuSaveAs })
2092table.insert(_mChoices, { name = "Print", key = "P", enabled = false, value = _printers })
2093table.insert(_mChoices, { name = "--------", enabled = false, value = nil })
2094table.insert(_mChoices, { name = "About", key = "b", enabled = true, value = displayAbout })
2095table.insert(_mChoices, { name = "Quit", key = "Q", enabled = true, value = function()
2096 if checkSave() then finished = true end end })
2097table.insert(_eChoices, { name = "Undo", key = "z", enabled = false, value = function()
2098 undoAction(undolist, redolist) end })
2099table.insert(_eChoices, { name = "Redo", key = "y", enabled = false, value = function()
2100 undoAction(redolist, undolist) end })
2101--table.insert(_eChoices, { name = "Cut", key = "x", enabled = false, value = function()
2102-- testPrint("unimplemented") end })
2103--table.insert(_eChoices, { name = "Copy", key = "c", enabled = false, value = function()
2104-- testPrint("unimplemented") end })
2105--table.insert(_eChoices, { name = "Paste", key = "v", enabled = false, value = function()
2106-- testPrint("unimplemented") end })
2107table.insert(_eChoices, { name = "-------", enabled = false, value = nil })
2108table.insert(_eChoices, { name = "Insert", key = "I", enabled = true, value = _iChoices })
2109table.insert(_eChoices, { name = "Align", key = "A", enabled = true, value = _aChoices })
2110
2111if not term.isColour() then
2112 print("Currently, WYSIwrite is only supported on advanced computers")
2113end
2114
2115local _resp = 0
2116--If there is a backup, we ask if this is to be restored
2117if (fs.exists(bPath)) then
2118 term.setBackgroundColour(colours.lightBlue)
2119 shell.run("clear")
2120 _resp = displayConfirmDialogue("Restore last document?", [[WYSIwrite unexpectedly closed last session. Would you like to restore your last document?]],
2121 {"Restore", colours.green}, {"Discard", colours.red})
2122 if _resp == 1 then
2123 loadDocument(bPath)
2124 end
2125 fs.delete(bPath)
2126end
2127
2128local _tArgs = {...}
2129if _tArgs[1] and _resp ~= 1 then
2130 sName = _tArgs[1]:gsub("\\","/")
2131 sPath = shell.resolve(sName)
2132 local _pcts = split(sName, "/")
2133 sName = _pcts[#_pcts]
2134 if sName == ".writebackup" then
2135 print("Unsafe to construct/overwrite backup with this application. If file exists, it can be restored by running write without a path.")
2136 return
2137 end
2138 if fs.exists(sPath) then loadDocument(sPath)
2139 else constructNewDocument(sName, sPath) end
2140elseif _resp ~= 1 then constructNewDocument() end
2141
2142checkYScroll()
2143while not finished do
2144 --Basic printing stuff
2145 if lscroll then
2146 displayClearPage()
2147 lscroll = false
2148 end
2149 term.setBackgroundColour(colours.white)
2150 term.setTextColour(colours.black)
2151 printPage(currpage, term, _xoff + 1, _yoff + yscroll)
2152 displayInterface()
2153 --Repositioning the cursor
2154 term.setTextColour(colours.black)
2155 term.setCursorPos(currind + _xoff + getWhitespaceLength(currpage,currline),
2156 currline + _yoff + yscroll)
2157
2158 handleInput()
2159end
2160--remove backup (this was a safe quit)
2161fs.delete(bPath)
2162term.setBackgroundColour(colours.black)
2163shell.run("clear")