View difference between Paste ID: 0YPSMpFT and iEsBufiN
SHOW: | | - or go back to the newest paste.
1
w = {}
2
debug = false
3
-- APIs
4
5
-- properties
6
local data = { }
7
local data_name = nil
8
local data_handlers = { }
9
10
local device_handlers = {}
11
12
local event_handlers = {}
13
local event_timers = {}
14
15
local monitors = {}
16
local monitor_textScale = 0.5
17
18
local page_handlers = {}
19
local page_endText = ""
20
local page_callbackDisplay
21
local page_callbackKey
22
23
local run_refreshPeriod_s = 3.0
24
local status_period_s = 1.0
25
26
local styles = {
27
  normal   = { front = colors.black    , back = colors.lightGray },
28
  good     = { front = colors.lime     , back = colors.lightGray },
29
  bad      = { front = colors.red      , back = colors.lightGray },
30
  disabled = { front = colors.gray     , back = colors.lightGray },
31
  help     = { front = colors.white    , back = colors.blue      },
32
  header   = { front = colors.orange   , back = colors.black     },
33
  control  = { front = colors.white    , back = colors.blue      },
34
  selected = { front = colors.black    , back = colors.lightBlue },
35
  warning  = { front = colors.white    , back = colors.red       },
36
  success  = { front = colors.white    , back = colors.lime      },
37
}
38
39
----------- Terminal & monitor support
40
41
local function setMonitorColorFrontBack(colorFront, colorBackground)
42
  term.setTextColor(colorFront)
43
  term.setBackgroundColor(colorBackground)
44
  if monitors ~= nil then
45
    for key, monitor in pairs(monitors) do
46
      monitor.setTextColor(colorFront)
47
      monitor.setBackgroundColor(colorBackground)
48
    end
49
  end
50
end
51
52
local function write(text)
53
  term.write(text)
54
  if monitors ~= nil then
55
    for key, monitor in pairs(monitors) do
56
      if key ~= data.radar_monitorIndex then
57
        monitor.write(text)
58
      end
59
    end
60
  end
61
end
62
63
local function getCursorPos()
64
  local x, y = term.getCursorPos()
65
  return x, y
66
end
67
68
local function setCursorPos(x, y)
69
  term.setCursorPos(x, y)
70
  if monitors ~= nil then
71
    for key, monitor in pairs(monitors) do
72
      if key ~= data.radar_monitorIndex then
73
        monitor.setCursorPos(x, y)
74
      end
75
    end
76
  end
77
end
78
79
local function getResolution()
80
  local sizeX, sizeY = term.getSize()
81
  return sizeX, sizeY
82
end
83
84
local function setColorNormal()
85
  w.setMonitorColorFrontBack(styles.normal.front, styles.normal.back)
86
end
87
88
local function setColorGood()
89
  w.setMonitorColorFrontBack(styles.good.front, styles.good.back)
90
end
91
92
local function setColorBad()
93
  w.setMonitorColorFrontBack(styles.bad.front, styles.bad.back)
94
end
95
96
local function setColorDisabled()
97
  w.setMonitorColorFrontBack(styles.disabled.front, styles.disabled.back)
98
end
99
100
local function setColorHelp()
101
  w.setMonitorColorFrontBack(styles.help.front, styles.help.back)
102
end
103
104
local function setColorHeader()
105
  w.setMonitorColorFrontBack(styles.header.front, styles.header.back)
106
end
107
108
local function setColorControl()
109
  w.setMonitorColorFrontBack(styles.control.front, styles.control.back)
110
end
111
112
local function setColorSelected()
113
  w.setMonitorColorFrontBack(styles.selected.front, styles.selected.back)
114
end
115
116
local function setColorWarning()
117
  w.setMonitorColorFrontBack(styles.warning.front, styles.warning.back)
118
end
119
120
local function setColorSuccess()
121
  w.setMonitorColorFrontBack(styles.success.front, styles.success.back)
122
end
123
124
local function clear(colorFront, colorBack)
125
  if colorFront == nil or colorBack == nil then
126
    w.setColorNormal()
127
  else
128
    w.setMonitorColorFrontBack(colorFront, colorBack)
129
  end
130
  term.clear()
131
  if monitors ~= nil then
132
    for key, monitor in pairs(monitors) do
133
      if key ~= data.radar_monitorIndex then
134
        monitor.clear()
135
      end
136
    end
137
  end
138
  w.setCursorPos(1, 1)
139
end
140
141
local function clearLine()
142
  term.clearLine()
143
  if monitors ~= nil then
144
    for key, monitor in pairs(monitors) do
145
      if key ~= data.radar_monitorIndex then
146
        monitor.clearLine()
147
      end
148
    end
149
  end
150
  local x, y = w.getCursorPos()
151
  w.setCursorPos(1, y)
152
end
153
154
local function writeLn(text)
155
  w.write(text)
156
  local x, y = w.getCursorPos()
157
  local xSize, ySize = w.getResolution()
158
  if y > ySize - 1 then
159
    y = 1
160
  end
161
  w.setCursorPos(1, y + 1)
162
end
163
164
local function writeMultiLine(text)
165
  local textToParse = text or ""
166
  for line in string.gmatch(textToParse, "[^\n]+") do
167
    if line ~= "" then
168
      w.writeLn(line)
169
    end
170
  end
171
end
172
173
local function writeCentered(y, text)
174
  local unused
175
  if text == nil then
176
    text = y
177
    unused, y = w.getCursorPos()
178
  end
179
  
180
  w.setCursorPos((51 - text:len()) / 2, y)
181
  term.write(text)
182
  if monitors ~= nil then
183
    for key, monitor in pairs(monitors) do
184
      if key ~= data.radar_monitorIndex then
185
        local xSize, ySize = monitor.getSize()
186
        if xSize ~= nil then
187
          monitor.setCursorPos((xSize - text:len()) / 2, y)
188
          monitor.write(text)
189
        end
190
      end
191
    end
192
  end
193
  w.setCursorPos(1, y + 1)
194
end
195
196
local function writeFullLine(text)
197
  w.write(text)
198
  local xSize, ySize = w.getResolution()
199
  local xCursor, yCursor = w.getCursorPos()
200
  for i = xCursor, xSize do
201
    w.write(" ")
202
  end
203
  w.setCursorPos(1, yCursor + 1)
204
end
205
206
----------- Page support
207
208
local function page_begin(text)
209
  w.clear()
210
  w.setCursorPos(1, 1)
211
  w.setColorHeader()
212
  w.clearLine()
213
  w.writeCentered(1, text)
214
  w.status_refresh()
215
  w.setCursorPos(1, 2)
216
  w.setColorNormal()
217
end
218
219
local function page_colors()
220
  w.clear(colors.white, colors.black)
221
  for key, value in pairs(colors) do
222
    local text = string.format("%12s", key)
223
    w.setMonitorColorFrontBack(colors.white, colors.black)
224
    w.write(text .. " ")
225
    w.setMonitorColorFrontBack(value, colors.black)
226
    w.write(" " .. text .. " ")
227
    w.setMonitorColorFrontBack(colors.black, value)
228
    w.write(" " .. text .. " ")
229
    w.setMonitorColorFrontBack(colors.white, value)
230
    w.write(" " .. text .. " ")
231
    w.setMonitorColorFrontBack(value, colors.white)
232
    w.write(" " .. text .. " ")
233
    w.writeLn("")
234
  end
235
  w.writeLn("")
236
  local index = 0
237
  for key, value in pairs(styles) do
238
    local text = string.format("%12s", key)
239
    if index % 2 == 0 then
240
      w.setMonitorColorFrontBack(colors.white, colors.black)
241
      w.write(text .. " ")
242
      w.setMonitorColorFrontBack(value.front, value.back)
243
      w.write(" " .. text .. " ")
244
    else
245
      w.setMonitorColorFrontBack(value.front, value.back)
246
      w.write(" " .. text .. " ")
247
      w.setMonitorColorFrontBack(colors.white, colors.black)
248
      w.write(text .. " ")
249
      w.writeLn("")
250
    end
251
    index = index + 1
252
  end
253
  w.setMonitorColorFrontBack(colors.white, colors.black)
254
end
255
256
local function page_end()
257
  w.setCursorPos(1, 18)
258
  w.setColorControl()
259
  w.writeFullLine(page_endText)
260
end
261
262
local function page_getCallbackDisplay()
263
  return page_callbackDisplay
264
end
265
266
local function page_register(index, callbackDisplay, callbackKey)
267
  page_handlers[index] = { display = callbackDisplay, key = callbackKey }
268
end
269
270
local function page_setEndText(text)
271
  page_endText = text
272
end
273
274
----------- Status line support
275
276
local status_clockTarget = -1 -- < 0 when stopped, < clock when elapsed, > clock when ticking
277
local status_isWarning = false
278
local status_line = 0
279
local status_text = ""
280
local function status_clear()
281
  if status_clockTarget > 0 then
282
    status_clockTarget = -1
283
    w.event_timer_stop("status")
284
    local xSize, ySize = w.getResolution()
285
    w.setCursorPos(1, ySize)
286
    w.setColorNormal()
287
    w.clearLine()
288
  end
289
end
290
local function status_isActive()
291
  return status_clockTarget > 0 and w.event_clock() < status_clockTarget
292
end
293
local function status_show(isWarning, text)
294
  if isWarning or not w.status_isActive() then
295
    status_line = 1
296
    status_isWarning = isWarning
297
    status_text = {}
298
    local textToParse = (text and text ~= "") and text or "???"
299
    for line in string.gmatch(textToParse, "[^\n]+") do
300
      if line ~= "" then
301
        table.insert(status_text, line)
302
      end
303
    end
304
    if isWarning then
305
      status_clockTarget = w.event_clock() + 1.0 * #status_text
306
    else
307
      status_clockTarget = w.event_clock() + 0.5 * #status_text
308
    end
309
    w.event_timer_start("status", status_period_s, "timer_status")
310
  end
311
  -- always refresh as a visual clue
312
  w.status_refresh()
313
end
314
local function status_refresh()
315
  if status_clockTarget > 0 then
316
    if w.event_clock() > status_clockTarget and status_line == 1 then
317
      w.status_clear()
318
    else
319
      local xSize, ySize = w.getResolution()
320
      w.setCursorPos(1, ySize)
321
      w.setColorNormal()
322
      w.clearLine()
323
324
      if status_isWarning then
325
        w.setColorWarning()
326
      else
327
        w.setColorSuccess()
328
      end
329
      local text = status_text[status_line]
330
      w.writeCentered(" " .. text .. " ")
331
      w.setColorNormal()
332
    end
333
  end
334
end
335
local function status_showWarning(text)
336
  w.status_show(true, text)
337
end
338
local function status_showSuccess(text)
339
  w.status_show(false, text)
340
end
341
local function status_tick()
342
  if status_clockTarget > -1 then
343
    local clockCurrent = w.event_clock()
344
    if clockCurrent > status_clockTarget then
345
      w.status_clear()
346
    else
347
      status_line = (status_line % #status_text) + 1
348
      w.status_refresh()
349
    end
350
  end
351
end
352
353
----------- Formatting
354
355
local function format_float(value, nbchar)
356
  local str = "?"
357
  if value ~= nil then
358
    if type(value) == "number" then
359
      str = string.format("%g", value)
360
    else
361
      str = type(value)
362
    end
363
  end
364
  if nbchar ~= nil then
365
    str = string.sub("               " .. str, -nbchar)
366
  end
367
  return str
368
end
369
370
local function format_integer(value, nbchar)
371
  local str = "?"
372
  if value ~= nil then
373
    if type(value) == "number" then
374
      str = string.format("%d", math.floor(value))
375
    else
376
      str = type(value)
377
    end
378
  end
379
  if nbchar ~= nil then
380
    str = string.sub("               " .. str, -nbchar)
381
  end
382
  return str
383
end
384
385
local function format_boolean(value, strTrue, strFalse)
386
  if value ~= nil then
387
    if type(value) == "boolean" then
388
      if value then
389
        return strTrue
390
      else
391
        return strFalse
392
      end
393
    else
394
      return type(value)
395
    end
396
  end
397
  return "?"
398
end
399
400
local function format_string(value, nbchar)
401
  local str = "?"
402
  if value ~= nil then
403
    str = "" .. value
404
  end
405
  if nbchar ~= nil then
406
    if #str > math.abs(nbchar) then
407
      str = string.sub(str, 1, math.abs(nbchar) - 1) .. "~"
408
    else
409
      str = string.sub(str .. "                                                  ", 1, nbchar)
410
    end
411
  end
412
  return str
413
end
414
415
local function format_address(value)
416
  local str = "?"
417
  if value ~= nil then
418
    str = "" .. value
419
  end
420
  str = string.sub(str, 10, 100)
421
  return str
422
end
423
424
local function format_charNumber(value)
425
  if  value ~= nil
426
  and type(value) == "number"
427
  and value <= 255
428
  and value >= 0 then
429
    return string.char(value)
430
  else
431
    return string.char(0)
432
  end
433
end
434
435
436
----------- Input controls
437
438
local function input_readText(currentValue)
439
  local inputAbort = false
440
  local input = w.format_string(currentValue)
441
  local ignoreNextChar = false
442
  local x, y = w.getCursorPos()
443
  
444
  term.setCursorBlink(true)
445
  repeat
446
    -- update display clearing extra characters
447
    w.setCursorPos(x, y)
448
    w.setColorNormal()
449
    w.write(w.format_string(input, 37))
450
    -- truncate input and set caret position
451
    input = string.sub(input, -36)
452
    w.setCursorPos(x + #input, y)
453
    
454
    local params = { os.pullEventRaw() }
455
    local eventName = params[1]
456
    local firstParam = params[2]
457
    if firstParam == nil then firstParam = "none" end
458
    if eventName == "key" then
459
      local keycode = params[2]
460
      
461
      if keycode == 14 then -- Backspace
462
        input = string.sub(input, 1, string.len(input) - 1)
463
        ignoreNextChar = true
464
      elseif keycode == 211 then -- Delete
465
        input = ""
466
        ignoreNextChar = true
467
      elseif keycode == 28 then -- Enter
468
        inputAbort = true
469
        ignoreNextChar = true
470
      else
471
        ignoreNextChar = false
472
        -- w.status_showWarning("Key " .. keycode .. " is not supported here")
473
      end
474
      
475
    elseif eventName == "char" then
476
      local character = params[2]
477
      if ignoreNextChar then
478
        ignoreNextChar = false
479
        -- w.status_showWarning("Ignored char #" .. string.byte(character) .. " '" .. character .. "'")
480
      elseif character >= ' ' and character <= '~' then -- any ASCII table minus controls and DEL
481
        input = input .. character
482
      else
483
        w.status_showWarning("Key '" .. character .. "' is not supported here (" .. string.byte(character) .. ")")
484
      end
485
      
486
    elseif eventName == "terminate" then
487
      inputAbort = true
488
      
489
    else
490
      local isSupported, needRedraw = w.event_handler(eventName, firstParam)
491
      if not isSupported then
492
        if not isSupported and debug then
493
        w.status_showWarning("Unsupported event: '" .. eventName .. "' with param: " .. firstParam)
494
      end
495
      end
496
    end
497
  until inputAbort
498
  term.setCursorBlink(false)
499
  w.setCursorPos(1, y + 1)
500
  if input == "" then
501
    return currentValue
502
  else
503
    return input
504
  end
505
end
506
507
local function input_readConfirmation(message)
508
  -- Set default message if not provided
509
  if not message then
510
    message = "Are you sure? (Y/n)"
511
  end
512
513
  -- Display the initial warning message
514
  w.status_showWarning(message)
515
516
  -- Event loop
517
  while true do
518
    local params = { os.pullEventRaw() }
519
    local eventName, firstParam = params[1], params[2] or "none"
520
521
    -- Handle key events
522
    if eventName == "key" then
523
      local keycode = firstParam
524
      if keycode == 28 then -- Enter key (Return)
525
        w.status_clear()
526
        return true
527
      end
528
529
    -- Handle character input (Y/n)
530
    elseif eventName == "char" then
531
      local character = firstParam
532
      w.status_clear()
533
      if character == 'y' or character == 'Y' then
534
        return true
535
      elseif character == 'n' or character == 'N' then
536
        return false
537
      end
538
539
    -- Handle termination (escape)
540
    elseif eventName == "terminate" then
541
      return false
542
543
    -- Handle unsupported events
544
    else
545
      local isSupported, needRedraw = w.event_handler(eventName, firstParam)
546
      if not isSupported and debug then
547
        w.status_showWarning("Unsupported event: '" .. eventName .. "' with param: " .. firstParam)
548
      end
549
    end
550
551
    -- Keep showing the message if status is inactive
552
    if not w.status_isActive() then
553
      w.status_showWarning(message)
554
    end
555
  end
556
end
557
558
local function input_readEnum(currentValue, list, toValue, toDescription, noValue)
559
  local inputAbort = false
560
  local inputKey = nil
561
  local input = nil
562
  local inputDescription = nil
563
  local ignoreNextChar = false
564
  local x, y = w.getCursorPos()
565
  
566
  w.setCursorPos(1, 17)
567
  for key, entry in pairs(list) do
568
    if toValue(entry) == currentValue then
569
      inputKey = key
570
    end
571
  end
572
  
573
  term.setCursorBlink(true)
574
  repeat
575
    w.setCursorPos(x, y)
576
    w.setColorNormal()
577
    if #list == 0 then
578
      inputKey = nil
579
    end
580
    if inputKey == nil then
581
      if currentValue ~= nil then
582
        input = noValue
583
        inputDescription = "Press enter to return previous entry"
584
      else
585
        input = noValue
586
        inputDescription = "Press enter to close listing"
587
      end
588
    else
589
      if inputKey < 1 then
590
        inputKey = #list
591
      elseif inputKey > #list then
592
        inputKey = 1
593
      end
594
      
595
      input = toValue(list[inputKey])
596
      inputDescription = toDescription(list[inputKey])
597
    end
598
    w.setColorNormal()
599
    w.write(input .. "                                                  ")
600
    w.setCursorPos(1, y + 1)
601
    w.setColorDisabled()
602
    w.write(inputDescription .. "                                                  ")
603
    
604
    local params = { os.pullEventRaw() }
605
    local eventName = params[1]
606
    local firstParam = params[2]
607
    if firstParam == nil then firstParam = "none" end
608
    if eventName == "key" then
609
      local keycode = params[2]
610
      
611
      if keycode == 14 or keycode == 211 then -- Backspace or Delete
612
        inputKey = nil
613
        ignoreNextChar = true
614
      elseif keycode == 200 or keycode == 203 or keycode == 78 then -- Up or Left or +
615
        if inputKey == nil then
616
          inputKey = 1
617
        else
618
          inputKey = inputKey - 1
619
        end
620
        ignoreNextChar = true
621
      elseif keycode == 208 or keycode == 205 or keycode == 74 then -- Down or Right or -
622
        if inputKey == nil then
623
          inputKey = 1
624
        else
625
          inputKey = inputKey + 1
626
        end
627
        ignoreNextChar = true
628
      elseif keycode == 28 then -- Enter
629
        inputAbort = true
630
        ignoreNextChar = true
631
      else
632
        ignoreNextChar = false
633
        -- w.status_showWarning("Key " .. keycode .. " is not supported here")
634
      end
635
      
636
    elseif eventName == "char" then
637
      local character = params[2]
638
      if ignoreNextChar then
639
        ignoreNextChar = false
640
        -- w.status_showWarning("Ignored char #" .. string.byte(character) .. " '" .. character .. "'")
641
      elseif character == '+' then -- +
642
        if inputKey == nil then
643
          inputKey = 1
644
        else
645
          inputKey = inputKey - 1
646
        end
647
      elseif character == '-' then -- -
648
        if inputKey == nil then
649
          inputKey = 1
650
        else
651
          inputKey = inputKey + 1
652
        end
653
      else
654
        w.status_showWarning("Key '" .. character .. "' is not supported here (" .. string.byte(character) .. ")")
655
      end
656
      
657
    elseif eventName == "terminate" then
658
      inputAbort = true
659
      
660
    elseif not w.event_handler(eventName, firstParam) then
661
      if not isSupported and debug then
662
        w.status_showWarning("Unsupported event: '" .. eventName .. "' with param: " .. firstParam)
663
      end
664
    end
665
  until inputAbort
666
  term.setCursorBlink(false)
667
  w.setCursorPos(1, y + 1)
668
  w.clearLine()
669
  if inputKey == nil then
670
    return nil
671
  else
672
    return toValue(list[inputKey])
673
  end
674
end
675
676
677
----------- Event handlers
678
679
local function reboot()
680
  os.reboot()
681
end
682
683
local function sleep(delay)
684
  os.sleep(delay)
685
end
686
687
-- return a global clock measured in second
688
local function event_clock()
689
  return os.clock()
690
end
691
692
local function event_timer_start(name_, period_s_, eventId_)
693
  local name = name_ or "-nameless-"
694
  local eventId = eventId_ or "timer_" .. name
695
  -- check for an already active timer
696
  local countActives = 0
697
  for id, entry in pairs(event_timers) do
698
    if entry.name == name and entry.active then -- already one started
699
      countActives = countActives + 1
700
    end
701
  end
702
  if countActives > 0 then
703
    if name ~= "status" then -- don't report status timer overlaps to prevent a stack overflow
704
      w.status_showWarning("Timer already started for " .. name)
705
    end
706
    return
707
  end
708
  -- start a new timer
709
  local period_s = period_s_ or 1.0
710
  local id = os.startTimer(period_s)
711
  event_timers[id] = {
712
    active = true,
713
    eventId = eventId,
714
    name = name,
715
    period_s = period_s
716
  }
717
end
718
719
local function event_timer_stop(name_)
720
  local name = name_ or "-nameless-"
721
  for id, entry in pairs(event_timers) do
722
    if entry.name == name then
723
      if entry.active then -- kill any active one
724
        entry.active = false
725
        os.cancelTimer(id)
726
      else -- purge all legacy ones
727
        event_timers[id] = nil
728
      end
729
    end
730
  end
731
end
732
733
local function event_timer_stopAll()
734
  for id, entry in pairs(event_timers) do
735
    if entry.active then
736
      event_timers[id] = nil
737
      os.cancelTimer(id)
738
    end
739
  end
740
end
741
742
local function event_timer_tick(id)
743
  local entry = event_timers[id]
744
  local isUnknown = entry == nil
745
  if isUnknown then -- unknown id, report a warning
746
    if debug then w.status_showWarning("Timer #" .. id .. " is unknown") end
747
    return
748
  end
749
  if not entry.active then -- dying timer, just ignore it
750
    return
751
  end
752
  -- resolve the timer
753
  os.queueEvent(entry.eventId, nil, nil, nil)
754
  -- CC timers are one shot, so we start a new one, if still needed
755
  if entry.active then
756
    entry.active = false
757
    w.event_timer_start(entry.name, entry.period_s)
758
  end
759
end
760
761
local function event_register(eventName, callback)
762
  event_handlers[eventName] = callback
763
end
764
765
-- returns isSupported, needRedraw
766
local function event_handler(eventName, param)
767
768
	local eventTable = {
769
	"key_up",
770
    "mouse_up",
771
    "mouse_drag",
772
    "mouse_scroll",
773
    "monitor_touch",
774
    "monitor_resize",
775
    "disk",
776
    "disk_eject",
777
    "peripheral",
778
    "peripheral_detach"--,
779
   -- "virtualAssistantCommand"
780
	}
781
782
  local needRedraw = false
783
  if event_handlers[eventName] ~= nil then
784
    needRedraw = event_handlers[eventName](eventName, param)
785
  elseif eventName == "redstone" then
786
    w.event_redstone(param)
787
  elseif eventName == "timer" then
788
    w.event_timer_tick(param)
789
  elseif eventName == "mouse_click" then
790
    w.status_showSuccess("Use the keyboard, Luke!")
791
  elseif eventName == eventTable then
792
  -- not supported: task_complete, rednet_message, modem_message
793
  else
794
    return false, needRedraw
795
  end
796
  return true, needRedraw
797
end
798
799
----------- Redstone support
800
801
local tblRedstoneState = {-- Remember redstone state on each side
802
  ["top"] = rs.getInput("top"),
803
  ["front"] = rs.getInput("front"),
804
  ["left"] = rs.getInput("left"),
805
  ["right"] = rs.getInput("right"),
806
  ["back"] = rs.getInput("back"),
807
  ["bottom"] = rs.getInput("bottom"),
808
}
809
810
local function event_redstone()
811
  -- Event only returns nil so we need to check sides manually
812
  local message = ""
813
  for side, state in pairs(tblRedstoneState) do
814
    if rs.getInput(side) ~= state then
815
      -- print(side .. " is now " .. tostring(rs.getInput(side)))
816
      message = message .. side .. " "
817
      tblRedstoneState[side] = rs.getInput(side)
818
    end
819
  end
820
  if message ~= "" then
821
    message = "Redstone changed on " .. message
822
    w.status_showWarning(message)
823
  end
824
end
825
826
----------- Configuration
827
828
local function data_get()
829
  return data
830
end
831
832
local function data_inspect(key, value)
833
  local stringValue = type(value) .. ","
834
  if type(value) == "boolean" then
835
    if value then
836
      stringValue = "true,"
837
    else
838
      stringValue = "false,"
839
    end
840
  elseif type(value) == "number" then
841
    stringValue = value .. ","
842
  elseif type(value) == "string" then
843
    stringValue = "'" .. value .. "',"
844
  elseif type(value) == "table" then
845
    stringValue = "{"
846
  end
847
  print(" " .. key .. " = " .. stringValue)
848
  if type(value) == "table" then
849
    for subkey, subvalue in pairs(value) do
850
      w.data_inspect(subkey, subvalue)
851
    end
852
    print("}")
853
  end
854
end
855
856
local function data_read()
857
  w.data_shouldUpdateName()
858
  
859
  data = { }
860
  if fs.exists("shipdata.txt") then
861
    local size = fs.getSize("shipdata.txt")
862
    if size > 0 then
863
      local file = fs.open("shipdata.txt", "r")
864
      if file ~= nil then
865
        local rawData = file.readAll()
866
        if rawData ~= nil then
867
          data = textutils.unserialize(rawData)
868
        end
869
        file.close()
870
        if data == nil then
871
          data = {}
872
        end
873
      end
874
    end
875
  end
876
  
877
  for name, handlers in pairs(data_handlers) do
878
    handlers.read(data)
879
  end
880
end
881
882
local function data_save()
883
  for name, handlers in pairs(data_handlers) do
884
    handlers.save(data)
885
  end
886
  
887
  local file = fs.open("shipdata.txt", "w")
888
  if file ~= nil then
889
    file.writeLine(textutils.serialize(data))
890
    file.close()
891
  else
892
    w.status_showWarning("No file system")
893
    w.sleep(3.0)
894
  end
895
end
896
897
local function data_getName()
898
  if data_name ~= nil then
899
    return data_name
900
  else
901
    return "-noname-"
902
  end
903
end
904
905
local function data_setName()
906
  -- check if any named component is connected
907
  local componentName = "computer"
908
  for name, handlers in pairs(data_handlers) do
909
    if handlers.name ~= nil then
910
      componentName = name
911
    end
912
  end
913
  
914
  -- ask for a new name
915
  w.page_begin("<==== Set " .. componentName .. " name ====>")
916
  w.setCursorPos(1, 4)
917
  w.setColorHelp()
918
  w.writeFullLine(" Press enter to validate.")
919
  w.setCursorPos(1, 3)
920
  w.setColorNormal()
921
  w.write("Enter " .. componentName .. " name: ")
922
  data_name = w.input_readText(data_name)
923
  
924
  -- update computer name
925
  os.setComputerLabel(data_name)
926
  
927
  -- update connected components
928
  for name, handlers in pairs(data_handlers) do
929
    if handlers.name ~= nil then
930
      handlers.name(data_name)
931
    end
932
  end
933
  
934
  -- w.reboot() -- not needed
935
end
936
937
local function data_shouldUpdateName()
938
  local shouldUpdateName = false
939
  
940
  -- check computer name
941
  data_name = os.getComputerLabel()
942
  if data_name == nil then
943
    shouldUpdateName = true
944
    data_name = "" .. os.getComputerID()
945
  end
946
  
947
  -- check connected components names
948
  for name, handlers in pairs(data_handlers) do
949
    if handlers.name ~= nil then
950
      local componentName = handlers.name()
951
      if componentName == "" then
952
        shouldUpdateName = true
953
      elseif shouldUpdateName then
954
        data_name = componentName
955
      elseif data_name ~= componentName then
956
        shouldUpdateName = true
957
        data_name = componentName
958
      end
959
    end
960
  end
961
  
962
  return shouldUpdateName
963
end
964
965
local function data_splitString(source, sep_)
966
  local sep = sep_ or ":"
967
  local fields = {}
968
  local pattern = string.format("([^%s]+)", sep)
969
  source:gsub(pattern, function(c) fields[#fields + 1] = c end)
970
  return fields
971
end
972
973
local function data_register(name, callbackRead, callbackSave, callbackName)
974
  -- read/save callbacks are always defined
975
  if callbackRead == nil then
976
    callbackRead = function() end
977
  end
978
  if callbackSave == nil then
979
    callbackSave = function() end
980
  end
981
  
982
  -- name callback is nil when not defined
983
  
984
  data_handlers[name] = { read = callbackRead, save = callbackSave, name = callbackName }
985
end
986
987
----------- Devices
988
989
local function device_get(address)
990
  return peripheral.wrap(address)
991
end
992
993
local function device_getMonitors()
994
  return monitors
995
end
996
997
local function device_register(deviceType, callbackRegister, callbackUnregister)
998
  device_handlers[deviceType] = { register = callbackRegister, unregister = callbackUnregister }
999
end
1000
1001
----------- Main loop
1002
1003
1004
local function boot()
1005
  if not term.isColor() then
1006
    print("Advanced computer required")
1007
    error()
1008
  end
1009
  print("loading...")
1010
  
1011
  math.randomseed(os.time())
1012
  
1013
  -- read configuration
1014
  w.data_read()
1015
  w.clear()
1016
  print("data_read...")
1017
  
1018
  -- initial scanning
1019
  monitors = {}
1020
  w.page_begin(data_name .. " - Connecting...")
1021
  w.writeLn("")
1022
  
1023
  local sides = peripheral.getNames()
1024
  for key, address in pairs(sides) do
1025
    w.sleep(0)
1026
    w.write("Checking " .. address .. " ")
1027
    local deviceType = peripheral.getType(address)
1028
    w.write(deviceType .. " ")
1029
    if deviceType == "monitor" then
1030
      w.write("wrapping!")
1031
      local lmonitor = w.device_get(address)
1032
      table.insert(monitors, lmonitor)
1033
      lmonitor.setTextScale(monitor_textScale)
1034
    else
1035
      local handlers = device_handlers[deviceType]
1036
      if handlers ~= nil then
1037
        w.write("wrapping!")
1038
        handlers.register(deviceType, address, w.device_get(address))
1039
      end
1040
    end
1041
    
1042
    w.writeLn("")
1043
  end
1044
  
1045
  -- synchronize computer and connected components names
1046
  local shouldUpdateName = w.data_shouldUpdateName()
1047
  if shouldUpdateName then
1048
    w.data_setName()
1049
  end
1050
  
1051
  -- peripheral boot up
1052
  if page_handlers['0'] == nil then
1053
    w.status_showWarning("Missing handler for connection page '0'!")
1054
    error()
1055
  end
1056
  page_handlers['0'].display(true)
1057
end
1058
1059
local function run()
1060
  local abort = false
1061
  local refresh = true
1062
  local ignoreNextChar = false
1063
  
1064
  local function selectPage(index)
1065
    if page_handlers[index] ~= nil then
1066
      page_callbackDisplay = page_handlers[index].display
1067
      page_callbackKey = page_handlers[index].key
1068
      refresh = true
1069
      return true
1070
    end
1071
    return false
1072
  end
1073
  
1074
  -- start refresh timer
1075
  w.event_register("timer_refresh", function() return page_callbackDisplay ~= page_handlers['0'].display end )
1076
  w.event_register("timer_status" , function() w.status_tick() return false end )
1077
  w.event_timer_start("refresh", run_refreshPeriod_s, "timer_refresh")
1078
  
1079
  -- register common events
1080
  w.event_register("enabled" , function(eventName, param) w.status_showWarning("Enabled '" .. w.format_string(param) .. "'") end )
1081
  w.event_register("disabled", function(eventName, param) w.status_showWarning("Disabled '" .. w.format_string(param) .. "'") end )
1082
  
1083
  -- main loop
1084
  selectPage('0')
1085
  repeat
1086
    if refresh then
1087
      w.clear()
1088
      page_callbackDisplay(false)
1089
      w.page_end()
1090
      refresh = false
1091
    end
1092
    local params = { os.pullEventRaw() }
1093
    local eventName = params[1]
1094
    local firstParam = params[2]
1095
    if firstParam == nil then firstParam = "none" end
1096
    -- w.writeLn("...")
1097
    -- w.writeLn("Event '" .. eventName .. "', " .. firstParam .. " received")
1098
    -- w.sleep(0.2)
1099
    
1100
    if eventName == "key" then
1101
      local keycode = params[2]
1102
      
1103
      ignoreNextChar = false
1104
      if keycode == 11 or keycode == 82 then -- 0
1105
        if selectPage('0') then
1106
          ignoreNextChar = true
1107
        end
1108
      elseif keycode == 2 or keycode == 79 then -- 1
1109
        if selectPage('1') then
1110
          ignoreNextChar = true
1111
        end
1112
      elseif keycode == 3 or keycode == 80 then -- 2
1113
        if selectPage('2') then
1114
          ignoreNextChar = true
1115
        end
1116
      elseif keycode == 4 or keycode == 81 then -- 3
1117
        if selectPage('3') then
1118
          ignoreNextChar = true
1119
        end
1120
      elseif keycode == 5 or keycode == 75 then -- 4
1121
        if selectPage('4') then
1122
          ignoreNextChar = true
1123
        end
1124
      elseif keycode == 6 or keycode == 76 then -- 5
1125
        if selectPage('5') then
1126
          ignoreNextChar = true
1127
        end
1128
	  elseif keycode == 7 or keycode == 77 then -- 6
1129
        if selectPage('6') then
1130
          ignoreNextChar = true
1131
        end
1132
	  elseif keycode == 8 or keycode == 71 then -- 7
1133
        if selectPage('7') then
1134
          ignoreNextChar = true
1135
        end
1136
	  elseif keycode == 9 or keycode == 72 then -- 8
1137
        if selectPage('8') then
1138
          ignoreNextChar = true
1139
        end
1140
	  elseif keycode == 10 or keycode == 73 then -- 9
1141
        if selectPage('9') then
1142
          ignoreNextChar = true
1143
        end
1144
      elseif page_callbackKey ~= nil and page_callbackKey("", keycode) then
1145
        refresh = true
1146
      else
1147
        ignoreNextChar = false
1148
        -- w.status_showWarning("Key " .. keycode .. " is not supported here")
1149
      end
1150
      
1151
    elseif eventName == "char" then
1152
      local character = params[2]
1153
      if ignoreNextChar then
1154
        ignoreNextChar = false
1155
        -- w.status_showWarning("Ignored char #" .. string.byte(character) .. " '" .. character .. "'")
1156
--      elseif character == 'x' or character == 'X' then -- x for eXit
1157
--        -- os.pullEventRaw() -- remove key_up event
1158
--        abort = true
1159
      elseif character == '0' then
1160
        selectPage('0')
1161
      elseif character == '1' then
1162
        selectPage('1')
1163
      elseif character == '2' then
1164
        selectPage('2')
1165
      elseif character == '3' then
1166
        selectPage('3')
1167
      elseif character == '4' then
1168
        selectPage('4')
1169
      elseif character == '5' then
1170
        selectPage('5')
1171
	  elseif character == '6' then
1172
        selectPage('6')
1173
      elseif character == '7' then
1174
        selectPage('7')
1175
      elseif character == '8' then
1176
        selectPage('8')
1177
      elseif character == '9' then
1178
        selectPage('9')
1179
		
1180
      elseif page_callbackKey ~= nil and page_callbackKey(character, -1) then
1181
        refresh = true
1182
      elseif string.byte(character) ~= 0 then -- not a control char
1183
        w.status_showWarning("Key '" .. character .. "' is not supported here (" .. string.byte(character) .. ")")
1184
      end
1185
      
1186
    elseif eventName == "terminate" then
1187
      abort = true
1188
      
1189
    else
1190
      local isSupported, needRedraw = w.event_handler(eventName, firstParam)
1191
      if not isSupported then
1192
      --  w.status_showWarning("Event '" .. eventName .. "', " .. firstParam .. " is unsupported")
1193
      end
1194
      refresh = needRedraw
1195
    end
1196
  until abort
1197
  
1198
  -- stop refresh timer
1199
  w.event_timer_stop("refresh")
1200
  w.event_timer_stopAll()
1201
end
1202
1203
local function close()
1204
  w.clear(colors.white, colors.black)
1205
  for key, handlers in pairs(device_handlers) do
1206
    w.writeLn("Closing " .. key)
1207
    if handlers.unregister ~= nil then
1208
      handlers.unregister(key)
1209
    end
1210
  end
1211
  
1212
  w.clear(colors.white, colors.black)
1213
  w.setCursorPos(1, 1)
1214
  w.writeLn("Program closed")
1215
  w.writeLn("Type startup to return to home page")
1216
end
1217
1218
-- we simulate LUA library behavior, so code is closer between CC and OC
1219
w = {
1220
  setMonitorColorFrontBack = setMonitorColorFrontBack,
1221
  write = write,
1222
  getCursorPos = getCursorPos,
1223
  setCursorPos = setCursorPos,
1224
  getResolution = getResolution,
1225
  setColorNormal = setColorNormal,
1226
  setColorGood = setColorGood,
1227
  setColorBad = setColorBad,
1228
  setColorDisabled = setColorDisabled,
1229
  setColorHelp = setColorHelp,
1230
  setColorHeader = setColorHeader,
1231
  setColorControl = setColorControl,
1232
  setColorSelected = setColorSelected,
1233
  setColorWarning = setColorWarning,
1234
  setColorSuccess = setColorSuccess,
1235
  clear = clear,
1236
  clearLine = clearLine,
1237
  writeLn = writeLn,
1238
  writeMultiLine = writeMultiLine,
1239
  writeCentered = writeCentered,
1240
  writeFullLine = writeFullLine,
1241
  page_begin = page_begin,
1242
  page_colors = page_colors,
1243
  page_end = page_end,
1244
  page_getCallbackDisplay = page_getCallbackDisplay,
1245
  page_register = page_register,
1246
  page_setEndText = page_setEndText,
1247
  status_clear = status_clear,
1248
  status_isActive = status_isActive,
1249
  status_show = status_show,
1250
  status_refresh = status_refresh,
1251
  status_showWarning = status_showWarning,
1252
  status_showSuccess = status_showSuccess,
1253
  status_tick = status_tick,
1254
  format_float = format_float,
1255
  format_integer = format_integer,
1256
  format_boolean = format_boolean,
1257
  format_string = format_string,
1258
  format_address = format_address,
1259
  format_charNumber = format_charNumber,
1260
  input_readInteger = input_readInteger,
1261
  input_readText = input_readText,
1262
  input_readConfirmation = input_readConfirmation,
1263
  input_readEnum = input_readEnum,
1264
  reboot = reboot,
1265
  sleep = sleep,
1266
  event_clock = event_clock,
1267
  event_timer_start = event_timer_start,
1268
  event_timer_stop = event_timer_stop,
1269
  event_timer_stopAll = event_timer_stopAll,
1270
  event_timer_tick = event_timer_tick,
1271
  event_register = event_register,
1272
  event_handler = event_handler,
1273
  event_redstone = event_redstone,
1274
  data_get = data_get,
1275
  data_inspect = data_inspect,
1276
  data_read = data_read,
1277
  data_save = data_save,
1278
  data_getName = data_getName,
1279
  data_setName = data_setName,
1280
  data_shouldUpdateName = data_shouldUpdateName,
1281
  data_splitString = data_splitString,
1282
  data_register = data_register,
1283
  device_get = device_get,
1284
  device_getMonitors = device_getMonitors,
1285
  device_register = device_register,
1286
  boot = boot,
1287
  run = run,
1288
  close = close,
1289
}