badline 0.16.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +213 -0
  3. data/CLAUDE.md +11 -7
  4. data/CONTRIBUTING.md +7 -1
  5. data/README.md +265 -26
  6. data/doc/media.md +5 -2
  7. data/doc/pinned-behaviour.md +189 -0
  8. data/doc/snapshot-format.md +616 -74
  9. data/doc/snapshots.md +192 -24
  10. data/exe/badline-ruby +5 -0
  11. data/lib/badline/audio/terminal.rb +34 -2
  12. data/lib/badline/c128/keyed_state.rb +2 -0
  13. data/lib/badline/c128.rb +1 -0
  14. data/lib/badline/c64/keyed_state.rb +2 -0
  15. data/lib/badline/c64.rb +1 -0
  16. data/lib/badline/cbm2/acia.rb +10 -0
  17. data/lib/badline/cbm2/bus.rb +263 -0
  18. data/lib/badline/cbm2/cpu.rb +58 -0
  19. data/lib/badline/cbm2/crtc_video.rb +133 -0
  20. data/lib/badline/cbm2/ieee_drive.rb +18 -0
  21. data/lib/badline/cbm2/ieee_layout.rb +69 -0
  22. data/lib/badline/cbm2/ieee_ports.rb +104 -0
  23. data/lib/badline/cbm2/ieee_traps.rb +81 -0
  24. data/lib/badline/cbm2/keyboard_ports.rb +60 -0
  25. data/lib/badline/cbm2/keyed_modules.rb +67 -0
  26. data/lib/badline/cbm2/keyed_state.rb +95 -0
  27. data/lib/badline/cbm2/model.rb +44 -0
  28. data/lib/badline/cbm2/p500/control_ports.rb +80 -0
  29. data/lib/badline/cbm2/p500/ieee_layout.rb +41 -0
  30. data/lib/badline/cbm2/p500/keyed_state.rb +85 -0
  31. data/lib/badline/cbm2/p500/model.rb +39 -0
  32. data/lib/badline/cbm2/p500/pages.rb +57 -0
  33. data/lib/badline/cbm2/p500.rb +264 -0
  34. data/lib/badline/cbm2/sid_page.rb +19 -0
  35. data/lib/badline/cbm2.rb +270 -0
  36. data/lib/badline/{plus4 → chips}/acia/frames.rb +1 -1
  37. data/lib/badline/chips/acia/keyed_state.rb +47 -0
  38. data/lib/badline/chips/acia.rb +307 -0
  39. data/lib/badline/chips/cia/timer.rb +2 -2
  40. data/lib/badline/chips/cpu/instruction_set.rb +1 -1
  41. data/lib/badline/chips/crtc.rb +3 -2
  42. data/lib/badline/chips/m6809/arithmetic.rb +140 -0
  43. data/lib/badline/chips/m6809/bus_steps.rb +223 -0
  44. data/lib/badline/chips/m6809/core.rb +149 -0
  45. data/lib/badline/chips/m6809/indexed.rb +82 -0
  46. data/lib/badline/chips/m6809/interrupts.rb +159 -0
  47. data/lib/badline/chips/m6809/keyed_state.rb +147 -0
  48. data/lib/badline/chips/m6809/opcodes.rb +159 -0
  49. data/lib/badline/chips/m6809/operations.rb +258 -0
  50. data/lib/badline/chips/m6809/plans.rb +143 -0
  51. data/lib/badline/chips/m6809/registers.rb +82 -0
  52. data/lib/badline/chips/m6809/stack.rb +140 -0
  53. data/lib/badline/chips/m6809.rb +13 -0
  54. data/lib/badline/chips/riot/fast_forward.rb +72 -0
  55. data/lib/badline/chips/riot/keyed_parts.rb +69 -0
  56. data/lib/badline/chips/riot/timer.rb +91 -0
  57. data/lib/badline/chips/riot.rb +162 -0
  58. data/lib/badline/chips/rriot/keyed_state.rb +30 -0
  59. data/lib/badline/chips/rriot.rb +134 -0
  60. data/lib/badline/chips/sid/filter.rb +4 -4
  61. data/lib/badline/chips/sid/waveform.rb +6 -6
  62. data/lib/badline/chips/tpi/keyed_state.rb +38 -0
  63. data/lib/badline/chips/tpi.rb +233 -0
  64. data/lib/badline/chips/vic/bank.rb +60 -5
  65. data/lib/badline/chips/vic/sprite.rb +4 -3
  66. data/lib/badline/chips/vic_i/keyed_parts.rb +7 -2
  67. data/lib/badline/chips/vic_i.rb +84 -21
  68. data/lib/badline/data_path.rb +3 -3
  69. data/lib/badline/drives/datasette/keyed_state.rb +9 -1
  70. data/lib/badline/drives/datasette.rb +27 -3
  71. data/lib/badline/drives/ieee_models.rb +54 -0
  72. data/lib/badline/drives/model1001.rb +37 -0
  73. data/lib/badline/drives/model2031.rb +6 -0
  74. data/lib/badline/drives/model4040/disk.rb +62 -0
  75. data/lib/badline/drives/model4040/ip_bus.rb +46 -0
  76. data/lib/badline/drives/model4040/keyed_state.rb +57 -0
  77. data/lib/badline/drives/model4040/mechanism.rb +192 -0
  78. data/lib/badline/drives/model4040/parts.rb +20 -0
  79. data/lib/badline/drives/model4040.rb +179 -0
  80. data/lib/badline/drives/model8050/board.rb +77 -0
  81. data/lib/badline/drives/model8050/disk.rb +163 -0
  82. data/lib/badline/drives/model8050/dos_board.rb +91 -0
  83. data/lib/badline/drives/model8050/fdc_bus.rb +102 -0
  84. data/lib/badline/drives/model8050/idle.rb +181 -0
  85. data/lib/badline/drives/model8050/ieee_port.rb +147 -0
  86. data/lib/badline/drives/model8050/ip_bus.rb +93 -0
  87. data/lib/badline/drives/model8050/keyed_state.rb +163 -0
  88. data/lib/badline/drives/model8050/mechanism.rb +230 -0
  89. data/lib/badline/drives/model8050/parts.rb +37 -0
  90. data/lib/badline/drives/model8050.rb +131 -0
  91. data/lib/badline/drives/model9060/idle.rb +149 -0
  92. data/lib/badline/drives/model9060/keyed_state.rb +123 -0
  93. data/lib/badline/drives/model9060/parts.rb +98 -0
  94. data/lib/badline/drives/model9060/sasi_bus.rb +88 -0
  95. data/lib/badline/drives/model9060/sasi_commands.rb +168 -0
  96. data/lib/badline/drives/model9060/sasi_target.rb +161 -0
  97. data/lib/badline/drives/model9060.rb +195 -0
  98. data/lib/badline/frontend/app.rb +18 -3
  99. data/lib/badline/frontend/boot.rb +4 -3
  100. data/lib/badline/frontend/controls.rb +107 -15
  101. data/lib/badline/frontend/gamepads.rb +3 -3
  102. data/lib/badline/frontend/keys.rb +97 -2
  103. data/lib/badline/frontend/kim1_panel.rb +208 -0
  104. data/lib/badline/frontend/media_slots.rb +8 -1
  105. data/lib/badline/frontend/menu/drive_types.rb +56 -0
  106. data/lib/badline/frontend/menu/menu_dialogs.rb +61 -14
  107. data/lib/badline/frontend/menu/menu_media.rb +47 -14
  108. data/lib/badline/frontend/menu/menu_pages.rb +113 -4
  109. data/lib/badline/frontend/menu/menu_ports.rb +77 -4
  110. data/lib/badline/frontend/menu/pause_menu.rb +6 -2
  111. data/lib/badline/frontend/pet_keyboard.rb +45 -7
  112. data/lib/badline/frontend/screen.rb +1 -1
  113. data/lib/badline/frontend/screens.rb +40 -2
  114. data/lib/badline/frontend/teletype_pane.rb +152 -0
  115. data/lib/badline/frontend/timeline.rb +49 -11
  116. data/lib/badline/frontend.rb +3 -0
  117. data/lib/badline/kernal_trap/dos/disk_commands.rb +80 -0
  118. data/lib/badline/kernal_trap/dos/status.rb +3 -0
  119. data/lib/badline/kernal_trap/dos.rb +18 -5
  120. data/lib/badline/kernal_trap/ieee.rb +4 -2
  121. data/lib/badline/kernal_trap/ieee_layout.rb +95 -43
  122. data/lib/badline/kim1/bus.rb +117 -0
  123. data/lib/badline/kim1/cpu.rb +25 -0
  124. data/lib/badline/kim1/host_terminal.rb +74 -0
  125. data/lib/badline/kim1/key_entry.rb +74 -0
  126. data/lib/badline/kim1/keyed_modules.rb +53 -0
  127. data/lib/badline/kim1/keyed_state.rb +133 -0
  128. data/lib/badline/kim1/panel.rb +176 -0
  129. data/lib/badline/kim1/rriots.rb +28 -0
  130. data/lib/badline/kim1/sound.rb +29 -0
  131. data/lib/badline/kim1/tape.rb +116 -0
  132. data/lib/badline/kim1/teletype.rb +193 -0
  133. data/lib/badline/kim1.rb +190 -0
  134. data/lib/badline/machine/ieee_drives.rb +171 -0
  135. data/lib/badline/machine/keyboard_buffer.rb +20 -3
  136. data/lib/badline/machine/line_sound.rb +122 -0
  137. data/lib/badline/machine/settling.rb +4 -0
  138. data/lib/badline/machine.rb +62 -14
  139. data/lib/badline/media/cbm2_media.rb +29 -0
  140. data/lib/badline/media/extensions.rb +8 -4
  141. data/lib/badline/media/kim1_media.rb +150 -0
  142. data/lib/badline/media/pet_media.rb +3 -1
  143. data/lib/badline/media/program.rb +6 -3
  144. data/lib/badline/media/true_drive.rb +51 -25
  145. data/lib/badline/media.rb +23 -8
  146. data/lib/badline/options/family.rb +108 -6
  147. data/lib/badline/options/help.rb +58 -12
  148. data/lib/badline/options/table.rb +60 -14
  149. data/lib/badline/options/validation.rb +71 -11
  150. data/lib/badline/options.rb +46 -13
  151. data/lib/badline/pet/bus.rb +16 -2
  152. data/lib/badline/pet/cassettes.rb +22 -1
  153. data/lib/badline/pet/ieee_drive.rb +5 -64
  154. data/lib/badline/pet/ieee_ports.rb +7 -0
  155. data/lib/badline/pet/keyboard_ports.rb +9 -2
  156. data/lib/badline/pet/keyed_state.rb +4 -0
  157. data/lib/badline/pet/model.rb +11 -2
  158. data/lib/badline/pet/rom_sets.rb +1 -0
  159. data/lib/badline/pet/sound.rb +5 -105
  160. data/lib/badline/pet/superpet/acia.rb +12 -0
  161. data/lib/badline/pet/superpet/board.rb +221 -0
  162. data/lib/badline/pet/superpet/dongle.rb +81 -0
  163. data/lib/badline/pet/superpet/keyed_modules.rb +63 -0
  164. data/lib/badline/pet/superpet/keyed_parts.rb +76 -0
  165. data/lib/badline/pet/superpet/m6809.rb +32 -0
  166. data/lib/badline/pet/superpet/waterloo_traps.rb +199 -0
  167. data/lib/badline/pet/superpet.rb +216 -0
  168. data/lib/badline/pet.rb +33 -6
  169. data/lib/badline/plus4/acia.rb +4 -298
  170. data/lib/badline/plus4/keyed_parts.rb +0 -44
  171. data/lib/badline/plus4/keyed_state.rb +2 -0
  172. data/lib/badline/plus4.rb +2 -0
  173. data/lib/badline/roms/1001/fdc-251257-02a.rom +0 -0
  174. data/lib/badline/roms/4040/dos-1.2.rom +0 -0
  175. data/lib/badline/roms/4040/dos-2.1-rev.rom +0 -0
  176. data/lib/badline/roms/4040/dos-2.1.rom +0 -0
  177. data/lib/badline/roms/4040/fdc-901466-02.rom +0 -0
  178. data/lib/badline/roms/4040/fdc-901466-04.rom +0 -0
  179. data/lib/badline/roms/8050/dos-2.7.rom +0 -0
  180. data/lib/badline/roms/8050/fdc-901869-01.rom +0 -0
  181. data/lib/badline/roms/8050/fdc-901885-04.rom +0 -0
  182. data/lib/badline/roms/9060/dos-3.0.rom +0 -0
  183. data/lib/badline/roms/9060/sasi-300515-revb.rom +0 -0
  184. data/lib/badline/roms/README +277 -0
  185. data/lib/badline/roms/cbm2/basic128-02b.rom +0 -0
  186. data/lib/badline/roms/cbm2/basic128.rom +0 -0
  187. data/lib/badline/roms/cbm2/basic256.rom +0 -0
  188. data/lib/badline/roms/cbm2/characters-600.rom +0 -0
  189. data/lib/badline/roms/cbm2/characters-700.rom +0 -0
  190. data/lib/badline/roms/cbm2/kernal-03b.rom +0 -0
  191. data/lib/badline/roms/cbm2/kernal-04a.rom +0 -0
  192. data/lib/badline/roms/cbm2/p500-basic.rom +0 -0
  193. data/lib/badline/roms/cbm2/p500-kernal.rom +0 -0
  194. data/lib/badline/roms/kim1/6530-002.rom +0 -0
  195. data/lib/badline/roms/kim1/6530-003.rom +0 -0
  196. data/lib/badline/roms/pet/characters-superpet.rom +0 -0
  197. data/lib/badline/roms/pet/superpet-a000.rom +0 -0
  198. data/lib/badline/roms/pet/superpet-c000.rom +0 -0
  199. data/lib/badline/roms/pet/superpet-e000-50hz.rom +0 -0
  200. data/lib/badline/roms/pet/superpet-e000.rom +0 -0
  201. data/lib/badline/snapshot/cbm2_setup.rb +53 -0
  202. data/lib/badline/snapshot/container.rb +9 -2
  203. data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
  204. data/lib/badline/snapshot/field_doc.rb +59 -3
  205. data/lib/badline/snapshot/image.rb +65 -9
  206. data/lib/badline/snapshot/kim1_setup.rb +28 -0
  207. data/lib/badline/snapshot/pet_setup.rb +27 -11
  208. data/lib/badline/snapshot/storage_setup.rb +4 -1
  209. data/lib/badline/snapshot/vic20_setup.rb +11 -7
  210. data/lib/badline/snapshot/vice/cbm2.rb +93 -0
  211. data/lib/badline/snapshot/vice/cbm2_acia.rb +75 -0
  212. data/lib/badline/snapshot/vice/cbm2_crtc.rb +127 -0
  213. data/lib/badline/snapshot/vice/cbm2_mains.rb +49 -0
  214. data/lib/badline/snapshot/vice/cbm2_mem.rb +75 -0
  215. data/lib/badline/snapshot/vice/cbm2_restore.rb +204 -0
  216. data/lib/badline/snapshot/vice/cbm2_tpi.rb +58 -0
  217. data/lib/badline/snapshot/vice/cias.rb +13 -9
  218. data/lib/badline/snapshot/vice/p500.rb +96 -0
  219. data/lib/badline/snapshot/vice/p500_restore.rb +130 -0
  220. data/lib/badline/snapshot/vice/p500_vicii.rb +166 -0
  221. data/lib/badline/snapshot/vice/pet.rb +20 -10
  222. data/lib/badline/snapshot/vice/pet_cpu.rb +42 -5
  223. data/lib/badline/snapshot/vice/pet_crtc.rb +25 -17
  224. data/lib/badline/snapshot/vice/pet_drive.rb +180 -0
  225. data/lib/badline/snapshot/vice/pet_drive8050.rb +336 -0
  226. data/lib/badline/snapshot/vice/pet_mem.rb +49 -5
  227. data/lib/badline/snapshot/vice/pet_restore.rb +100 -7
  228. data/lib/badline/snapshot/vice/pet_via.rb +6 -5
  229. data/lib/badline/snapshot/vice/sid_registers.rb +2 -1
  230. data/lib/badline/snapshot/vice/vicii_import.rb +10 -6
  231. data/lib/badline/snapshot/vice.rb +3 -0
  232. data/lib/badline/snapshot.rb +15 -3
  233. data/lib/badline/status.rb +30 -30
  234. data/lib/badline/storage/crt_file.rb +7 -1
  235. data/lib/badline/storage/d64_image.rb +7 -0
  236. data/lib/badline/storage/d67_image.rb +55 -0
  237. data/lib/badline/storage/d80_image.rb +17 -1
  238. data/lib/badline/storage/d81_image.rb +14 -0
  239. data/lib/badline/storage/d90_image.rb +180 -0
  240. data/lib/badline/storage/disk_image/bam.rb +18 -3
  241. data/lib/badline/storage/disk_image/formatting.rb +69 -0
  242. data/lib/badline/storage/disk_image/writing.rb +37 -2
  243. data/lib/badline/storage/disk_image.rb +2 -0
  244. data/lib/badline/storage/kim_tape.rb +254 -0
  245. data/lib/badline/storage/papertape.rb +90 -0
  246. data/lib/badline/storage.rb +5 -0
  247. data/lib/badline/support/cbm2_checkpoint.rb +48 -0
  248. data/lib/badline/support/kim1_checkpoint.rb +41 -0
  249. data/lib/badline/support/p500_checkpoint.rb +35 -0
  250. data/lib/badline/support/superpet_checkpoint.rb +35 -0
  251. data/lib/badline/version.rb +1 -1
  252. data/lib/badline/vic20/keyed_modules.rb +6 -3
  253. data/lib/badline/vic20/keyed_state.rb +6 -4
  254. data/lib/badline/vic20.rb +26 -12
  255. data/lib/badline.rb +2 -0
  256. metadata +148 -2
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Frontend
5
+ # The pause menu's list of the true drives a PET or a CBM-II takes on
6
+ # device 8, which the DRIVE 8 page's DRIVE TYPE row brings up: a row
7
+ # for each model, the disks it reads at its right, and the one on the
8
+ # bus said so. Up and Down move along the list, Return or a click picks
9
+ # a model, and Left or Esc goes back to the page.
10
+ class DriveTypes
11
+ MODELS = %w[2031 2040 3040 4040 1001 8050 8250 9060 9090].freeze
12
+ ACTIONS = %i[type2031 type2040 type3040 type4040 type1001 type8050 type8250 type9060 type9090].freeze
13
+
14
+ def initialize(painter, buttons)
15
+ @painter = painter
16
+ @buttons = buttons
17
+ @open = false
18
+ @model = ""
19
+ end
20
+
21
+ def open? = @open
22
+
23
+ # Opens the list with the focus on `model`, the drive on the bus, or
24
+ # on the first if there's none.
25
+ def open(model)
26
+ @model = model
27
+ @open = true
28
+ index = MODELS.index(model)
29
+ @buttons.focus = ACTIONS[index.nil? ? 0 : index]
30
+ end
31
+
32
+ def close
33
+ @open = false
34
+ end
35
+
36
+ # The model `action` picks, or nil.
37
+ def model_for(action)
38
+ index = ACTIONS.index(action)
39
+ index.nil? ? nil : MODELS[index]
40
+ end
41
+
42
+ # Draws the list `width` wide.
43
+ def draw(left, top, width)
44
+ @painter.text(left, top, "DRIVE 8 TYPE", MenuTheme::BRIGHT)
45
+ top += 20
46
+ MODELS.each_with_index do |model, index|
47
+ media = Drives::IEEEModels::MEDIA.fetch(model).join(" ").upcase
48
+ label = model == @model ? "#{model}, ON THE BUS" : model
49
+ @buttons.row([left, top, width], label, ACTIONS[index], media)
50
+ top += Buttons::HEIGHT + 2
51
+ end
52
+ @painter.text(left, top + 6, "RETURN: PICK LEFT, ESC: BACK", MenuTheme::DIM)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -4,12 +4,14 @@ module Badline
4
4
  module Frontend
5
5
  # The pause menu's screens that take over its panel: the file browser
6
6
  # INSERT, Quick open and LOAD bring up, the question before a cartridge
7
- # or Quick open power cycles the machine, and the prompt for a save's
8
- # name. A disk or tape picked goes straight in, and a save loads.
7
+ # or Quick open power cycles the machine, the prompt for a save's name,
8
+ # and the list of a PET's or a CBM-II's drive types. A disk or tape
9
+ # picked goes straight in, a save loads, and a drive type picked goes on
10
+ # the bus.
9
11
  class MenuDialogs
10
12
  EXTENSIONS = {
11
- disk: Media::Extensions::DEVICE8, tape: Media::Extensions.of(%i[tape]),
12
- cartridge: Media::Extensions.of(%i[cartridge]), program: Media::Extensions::ALL, snapshot: %w[.vsf]
13
+ disk: Media::Extensions::DEVICE8, cartridge: Media::Extensions.of(%i[cartridge]),
14
+ program: Media::Extensions::ALL, snapshot: %w[.vsf]
13
15
  }.freeze
14
16
  TITLES = { disk: "INSERT A DISK", tape: "INSERT A TAPE", cartridge: "INSERT A CARTRIDGE",
15
17
  program: "QUICK OPEN", snapshot: "LOAD A SAVE" }.freeze
@@ -25,13 +27,14 @@ module Badline
25
27
  @browser = FileBrowser.new(painter, buttons)
26
28
  @confirmation = Confirmation.new(painter, buttons)
27
29
  @name_field = NameField.new(painter)
30
+ @drive_types = DriveTypes.new(painter, buttons)
28
31
  @snapshots = snapshots
29
32
  @browsing = nil
30
33
  @browse_from = media_path.empty? ? Dir.pwd : File.dirname(File.expand_path(media_path))
31
34
  @computer = nil
32
35
  end
33
36
 
34
- def open? = !@browsing.nil? || @confirmation.open? || @name_field.open?
37
+ def open? = !@browsing.nil? || @confirmation.open? || @name_field.open? || @drive_types.open?
35
38
 
36
39
  # Opens the browser on the files for `kind`: :disk, :tape, :cartridge,
37
40
  # :program, or :snapshot, which starts in the saves folder.
@@ -40,12 +43,16 @@ module Badline
40
43
  extensions = case kind
41
44
  when :cartridge then @media.cartridge_extensions
42
45
  when :disk then @media.disk_extensions
46
+ when :tape then @media.tape_extensions
43
47
  when :program then programs
44
48
  else EXTENSIONS.fetch(kind)
45
49
  end
46
50
  @browser.open(kind == :snapshot ? Badline.data_folder("saves") : @browse_from, extensions)
47
51
  end
48
52
 
53
+ # Lists the drive types, with `model` the one on the bus.
54
+ def pick_drive(model) = @drive_types.open(model)
55
+
49
56
  # Asks for a save's name, starting from `name`.
50
57
  def ask_name(name) = @name_field.open("SAVE AS", name)
51
58
 
@@ -66,7 +73,7 @@ module Badline
66
73
  def drop(path)
67
74
  extension = File.extname(path).downcase
68
75
  return @media.insert(:disk, path) && false if @media.disk_extensions.include?(extension)
69
- return @media.insert(:tape, path) && false if EXTENSIONS[:tape].include?(extension)
76
+ return @media.insert(:tape, path) && false if @media.tape_extensions.include?(extension)
70
77
  return false unless programs.include?(extension)
71
78
 
72
79
  @confirmation.ask(@media.cartridge_extensions.include?(extension) ? :cartridge : :start, path)
@@ -77,6 +84,7 @@ module Badline
77
84
  # answered yes, or nil.
78
85
  def key(scancode)
79
86
  return browsed(@browser.key(scancode)) unless @browsing.nil?
87
+ return drive_key(scancode) if @drive_types.open?
80
88
  if @name_field.open? && !@confirmation.open?
81
89
  return named(@name_field.key(scancode, SDL.event_mod(SDL.event).anybits?(SDL::KMOD_SHIFT)))
82
90
  end
@@ -88,15 +96,21 @@ module Badline
88
96
  nil
89
97
  end
90
98
 
91
- # Handles a click on the browser's list at `top`, or on a question's
92
- # answer, `action`.
93
- def click(action, top) = action == :browse ? browsed(@browser.click(top)) : answer(action)
99
+ # Handles a click on the browser's list at `top`, on a question's
100
+ # answer, `action`, or on a drive type.
101
+ def click(action, top)
102
+ return browsed(@browser.click(top)) if action == :browse
103
+ return picked(action) if @drive_types.open?
104
+
105
+ answer(action)
106
+ end
94
107
 
95
108
  def scroll(rows) = @browser.scroll(rows)
96
109
 
97
110
  def draw(left, top, width)
98
111
  return @browser.draw(left, top, width, TITLES.fetch(@browsing)) unless @browsing.nil?
99
112
  return @confirmation.draw(left, top) if @confirmation.open?
113
+ return @drive_types.draw(left, top, width) if @drive_types.open?
100
114
 
101
115
  @name_field.draw(left, top, width)
102
116
  end
@@ -104,12 +118,16 @@ module Badline
104
118
  private
105
119
 
106
120
  # What Quick open and a drop start: every medium, and the cartridges
107
- # the machine takes.
108
- def programs = (EXTENSIONS[:program] + @media.cartridge_extensions).uniq
121
+ # the machine takes, or on the KIM-1 its programs.
122
+ def programs
123
+ return Media::KIM1Media::EXTENSIONS if @media.kim1?
124
+
125
+ (EXTENSIONS[:program] + @media.cartridge_extensions).uniq
126
+ end
109
127
 
110
- # Puts the disk or tape picked in, or asks before a cartridge or
111
- # Quick open power cycles the machine, and leaves the browser, as Esc
112
- # does.
128
+ # Puts the disk or tape picked in, a tape Quick open picks on the
129
+ # KIM-1 too, or asks before a cartridge or Quick open power cycles the
130
+ # machine, and leaves the browser, as Esc does.
113
131
  def browsed(picked)
114
132
  return nil if picked.nil?
115
133
 
@@ -121,12 +139,41 @@ module Badline
121
139
 
122
140
  if %i[disk tape].include?(kind)
123
141
  @media.insert(kind, picked)
142
+ elsif @media.kim1? && @media.tape_extensions.include?(File.extname(picked).downcase)
143
+ @media.insert(:tape, picked)
124
144
  else
125
145
  @confirmation.ask(kind == :cartridge ? :cartridge : :start, picked)
126
146
  end
127
147
  nil
128
148
  end
129
149
 
150
+ # Up and Down move along the drive types, Return or Space picks one,
151
+ # and Left or Esc goes back to the page.
152
+ def drive_key(scancode)
153
+ return picked(@buttons.focus) if [Keys::RETURN, Keys::SPACE].include?(scancode)
154
+
155
+ direction = Keys::DIRECTIONS[scancode]
156
+ if scancode == Keys::ESCAPE || direction == :left
157
+ @drive_types.close
158
+ @buttons.focus = :drive_type
159
+ elsif %i[up down].include?(direction)
160
+ @buttons.shift(direction == :up ? -1 : 1, 1)
161
+ end
162
+ nil
163
+ end
164
+
165
+ # Puts the drive type `action` stands for on the bus, and goes back
166
+ # to the page.
167
+ def picked(action)
168
+ model = @drive_types.model_for(action)
169
+ return nil if model.nil?
170
+
171
+ @drive_types.close
172
+ @buttons.focus = :drive_type
173
+ @media.drive_type(model)
174
+ nil
175
+ end
176
+
130
177
  # Saves the machine under the name typed once Return takes it, and
131
178
  # asks first when a save has that name already.
132
179
  def named(result)
@@ -37,19 +37,28 @@ module Badline
37
37
 
38
38
  def writable? = @writable
39
39
 
40
+ def kim1? = @computer.family == :kim1
41
+
40
42
  def disk_path = MediaSlots.disk_path(@computer)
41
43
 
42
44
  def inserted? = MediaSlots.disk?(@computer)
43
45
 
44
- # Puts a disk in device 8 or a tape in the datasette, `kind` :disk or
45
- # :tape, and says whether it went in. A list puts its first disk in.
46
+ # Puts a disk in device 8 or a tape in the datasette or the KIM-1's
47
+ # deck, `kind` :disk or :tape, and says whether it went in. A list
48
+ # puts its first disk in.
46
49
  def insert(kind, path)
47
50
  succeeds? do
48
- if kind == :disk
51
+ if @computer.family == :kim1
52
+ raise Machine::Unsupported, "the KIM-1 takes no disks" if kind == :disk
53
+
54
+ @computer.tape.insert_tape(Storage::KIMTape.read(path))
55
+ elsif kind == :disk
49
56
  disk = Media::DiskList.disk(path)
50
57
  Media.insert_disk(@computer, disk, read_only: !@writable)
51
58
  list(path)
52
59
  else
60
+ raise Machine::Unsupported, "the CBM-II has no datasette" if @computer.family == :cbm2
61
+
53
62
  @computer.datasette.insert(Storage::TAP.new(path))
54
63
  end
55
64
  end
@@ -68,6 +77,21 @@ module Badline
68
77
 
69
78
  def eject_disk = MediaSlots.eject_disk(@computer)
70
79
 
80
+ # Puts a true drive of `model` on the PET's or the CBM-II's bus as
81
+ # device 8, in place of the drive or the traps there, with the disk
82
+ # device 8 had in it if the model reads it, and says whether it went
83
+ # on. The disk comes out first, so what was written reaches its file.
84
+ def drive_type(model)
85
+ return true if MediaSlots.drive_model(@computer) == model
86
+
87
+ succeeds? do
88
+ path = disk_path
89
+ eject_disk
90
+ @computer.plug_ieee_drive(model)
91
+ Media::TrueDrive.insert(@computer, path, read_only: !@writable) if Drives::IEEEModels.reads?(model, path)
92
+ end
93
+ end
94
+
71
95
  # Sets whether disks are writable, for the disk in the drive too,
72
96
  # which goes in again as the notch now says.
73
97
  def writable(writable)
@@ -99,18 +123,25 @@ module Badline
99
123
  insert(:disk, set[index + step])
100
124
  end
101
125
 
126
+ # The extensions of the tapes the datasette takes, or the KIM-1's
127
+ # deck: .wav recordings.
128
+ def tape_extensions = kim1? ? [Media::KIM1Media::TAPE] : Media::Extensions.of(%i[tape])
129
+
102
130
  # The extensions of the cartridges the machine takes: .crt, on the
103
- # Plus/4 raw .bin ROM images too, and none on the PET.
131
+ # Plus/4 raw .bin ROM images too, and none on the PET or the KIM-1,
132
+ # or on the CBM-II, whose cartridge goes in from the command line.
104
133
  def cartridge_extensions
105
- extensions = Media::Extensions.of(@computer.family == :pet ? %i[none] : %i[cartridge])
134
+ extensions = Media::Extensions.of(%i[pet cbm2 kim1].include?(@computer.family) ? %i[none] : %i[cartridge])
106
135
  @computer.family == :plus4 ? extensions + [Media::Plus4Media::ROM_IMAGE] : extensions
107
136
  end
108
137
 
109
- # The extensions of what goes in device 8: on the PET the 8050's and
110
- # 8250's disk images besides the others, and no raw GCR, which only
111
- # a serial true drive reads.
138
+ # The extensions of what goes in device 8: on the PET and the CBM-II
139
+ # the 8050's and 8250's disk images and the hard disks' besides the
140
+ # others, and no raw GCR, which only a serial true drive reads.
112
141
  def disk_extensions
113
- return Media::Extensions.of(%i[disk ieee_disk archive disk_list]) if @computer.family == :pet
142
+ if %i[pet cbm2].include?(@computer.family)
143
+ return Media::Extensions.of(%i[disk ieee_disk hard_disk archive disk_list])
144
+ end
114
145
 
115
146
  Media::Extensions::DEVICE8
116
147
  end
@@ -125,17 +156,19 @@ module Badline
125
156
 
126
157
  # What a save of the machine is named after: the cartridge, the disk,
127
158
  # the tape, or BASIC, without the bracketed parts of a file's name. A
128
- # VIC-20's are named after its disk, or else the machine, and a
129
- # Plus/4's or a PET's after its disk or tape.
159
+ # VIC-20's are named after its disk, or else the machine, a Plus/4's
160
+ # or a PET's after its disk or tape, and a CBM-II's after its disk.
130
161
  def game_name
131
162
  vic20 = @computer.family == :vic20
132
- name = vic20 || %i[plus4 pet].include?(@computer.family) ? "" : cartridge_name
163
+ name = vic20 || %i[plus4 pet cbm2 kim1].include?(@computer.family) ? "" : cartridge_name
133
164
  disk = disk_path
134
165
  name = File.basename(disk, File.extname(disk)) if name.empty? && !disk.empty?
135
- name = tape_name if name.empty? && !vic20
166
+ name = tape_name if name.empty? && !vic20 && !%i[cbm2 kim1].include?(@computer.family)
136
167
  name = name.sub(/\s*[(\[].*\z/, "").strip
137
168
  return name unless name.empty?
138
169
 
170
+ return "KIM-1" if @computer.family == :kim1
171
+
139
172
  vic20 ? "VIC-20" : "BASIC"
140
173
  end
141
174
 
@@ -154,7 +187,7 @@ module Badline
154
187
  case @computer.family
155
188
  when :plus4 then @computer.cartridge_slots.map { |slot| slot_name(slot) }.uniq.join(", ")
156
189
  when :vic20 then @computer.cartridge_name
157
- when :pet then ""
190
+ when :pet, :cbm2, :kim1 then ""
158
191
  else
159
192
  cartridge = @computer.address_bus.cartridge
160
193
  cartridge.nil? ? "" : cartridge.name
@@ -13,8 +13,20 @@ module Badline
13
13
  COLUMNS = 25
14
14
  ROW_WIDTH = 200
15
15
 
16
+ # The KIM-1's settings on its PANEL page (MenuPorts).
17
+ KIM1_SETTINGS = %i[sst_off sst_on keypad teletype irq_open irq_wired pb7_out pa0_speaker].freeze
18
+
19
+ # The KIM-1's deck on its TAPE page: the keys down, and the rest.
20
+ KIM1_DECK = [%w[STOP PLAY RECORD], %i[kim1_stop kim1_play kim1_record]].freeze
21
+ KIM1_TAPE = (KIM1_DECK.last + %i[kim1_rewind kim1_eject]).freeze
22
+
16
23
  RECENT = %i[recent0 recent1 recent2 recent3 recent4 recent5 recent6 recent7].freeze
17
24
 
25
+ # The SuperPET's switches: SW2 picks the CPU and SW1 protects the
26
+ # bank RAM, each at PROG left to the system latch.
27
+ CPU_SWITCH = [%w[6502 6809 PROG], %i[cpu6502 cpu6809 cpu_prog]].freeze
28
+ RAM_SWITCH = [%w[R/W READ PROG], %i[bank_rw bank_read bank_prog]].freeze
29
+
18
30
  SID_MODELS = %i[mos6581 mos8580].freeze
19
31
  RAM_NAMES = {
20
32
  unexpanded: "", "3k": "3K", "8k": "8K", "16k": "16K", "24k": "24K", "32k": "32K",
@@ -76,8 +88,12 @@ module Badline
76
88
  when :swap then @controls.swap_ports
77
89
  when :sound_on, :mute then @sound.toggle_mute unless @sound.muted? == (action == :mute)
78
90
  when :sid6581, :sid8580 then @computer.sound_source.model = action == :sid8580 ? :mos8580 : :mos6581
91
+ when :cpu6502, :cpu6809, :cpu_prog then switch_cpu(action)
92
+ when :bank_rw, :bank_read, :bank_prog then switch_bank_ram(action)
79
93
  when :reset then @computer.reset!
80
94
  when :power_cycle then @computer.power_cycle!
95
+ when *KIM1_SETTINGS then kim1_setting(action)
96
+ when *KIM1_TAPE then kim1_tape(action)
81
97
  else pick_port(action)
82
98
  end
83
99
  end
@@ -88,6 +104,21 @@ module Badline
88
104
  # Whether the machine is a PET, whose ports page shows its keyboard.
89
105
  def pet? = @computer.family == :pet
90
106
 
107
+ # Whether the machine is a CBM-II, whose ports page shows its keyboard
108
+ # too.
109
+ def cbm2? = @computer.family == :cbm2
110
+
111
+ # The model of the true drive on device 8, or an empty one for the
112
+ # traps.
113
+ def drive_model = MediaSlots.drive_model(@computer)
114
+
115
+ # Whether the machine is the CBM-II's B series, whose ports page is
116
+ # its keyboard's alone.
117
+ def b_series? = cbm2? && !Controls.p500?(@computer)
118
+
119
+ # Whether the machine is a KIM-1, whose ports page is its panel's.
120
+ def kim1? = @computer.family == :kim1
121
+
91
122
  private
92
123
 
93
124
  def vic20? = @computer.family == :vic20
@@ -95,6 +126,8 @@ module Badline
95
126
  def plus4? = @computer.family == :plus4
96
127
 
97
128
  def draw_drive
129
+ return info("DRIVE", "") if kim1?
130
+
98
131
  media = @media
99
132
  inserted = media.inserted?
100
133
  set = inserted ? media.disk_set : []
@@ -110,9 +143,14 @@ module Badline
110
143
  skip
111
144
  drive = @computer.true_drive
112
145
  info("DEVICE", drive.nil? ? "FAST LOADING" : "#{drive.model_name}, TRUE DRIVE")
146
+ row("DRIVE TYPE...", :drive_type, drive_model) if pet? || cbm2?
113
147
  end
114
148
 
149
+ # The CBM-II has no datasette, and the KIM-1 a deck of its own.
115
150
  def draw_datasette
151
+ return info("TAPE", "") if cbm2?
152
+ return draw_kim1_tape if kim1?
153
+
116
154
  datasette = @computer.datasette
117
155
  tape = datasette.tape
118
156
  info("TAPE", tape.nil? ? "" : File.basename(tape.path))
@@ -124,8 +162,55 @@ module Badline
124
162
  info("MOTOR", datasette.motor? ? "ON" : "OFF")
125
163
  end
126
164
 
165
+ # The KIM-1's deck, with its tape's name and how far into it the deck
166
+ # is, in minutes and seconds.
167
+ def draw_kim1_tape
168
+ deck = @computer.tape
169
+ tape = deck.tape
170
+ info("TAPE", File.basename(tape.path))
171
+ row("INSERT...", :insert_tape)
172
+ row("EJECT", :kim1_eject)
173
+ down = 0
174
+ down = 1 if deck.playing?
175
+ down = 2 if deck.recording?
176
+ toggle("DECK", :kim1_deck, [KIM1_DECK.first, KIM1_DECK.last, down])
177
+ row("REWIND", :kim1_rewind)
178
+ skip
179
+ info("POSITION", "#{clock(deck.position)} OF #{clock(tape.duration)}")
180
+ end
181
+
182
+ def clock(microseconds)
183
+ seconds = microseconds / 1_000_000
184
+ "#{seconds / 60}:#{(seconds % 60).to_s.rjust(2, '0')}"
185
+ end
186
+
187
+ # Presses the KIM-1 deck's keys. RECORD with no file to record to
188
+ # puts a new tape in first, in the tapes folder (Badline.data_folder).
189
+ def kim1_tape(action)
190
+ deck = @computer.tape
191
+ case action
192
+ when :kim1_stop then deck.stop
193
+ when :kim1_play then deck.play
194
+ when :kim1_record
195
+ deck.insert_tape(Storage::KIMTape.create(new_tape_path)) if deck.tape.path.empty?
196
+ deck.record
197
+ when :kim1_rewind then deck.rewind
198
+ else deck.eject
199
+ end
200
+ end
201
+
202
+ # The first of kim1-1.wav, kim1-2.wav and on that isn't taken.
203
+ def new_tape_path
204
+ folder = Badline.data_folder("tapes")
205
+ number = 1
206
+ number += 1 while File.exist?(File.join(folder, "kim1-#{number}.wav"))
207
+ File.join(folder, "kim1-#{number}.wav")
208
+ end
209
+
127
210
  def draw_expansion
128
211
  return draw_pet_memory if pet?
212
+ return info("RAM", "#{@computer.model.ram_kb}K") if cbm2?
213
+ return info("RAM", "1K") if kim1?
129
214
 
130
215
  info("CARTRIDGE", @media.cartridge_name)
131
216
  return info("RAM EXPANSION", RAM_NAMES.fetch(@computer.ram_configuration)) if vic20?
@@ -163,6 +248,8 @@ module Badline
163
248
  return draw_8296_memory if model.cbm8296?
164
249
 
165
250
  info("RAM", "#{@computer.ram_kb}K")
251
+ return info("BANK RAM", "64K, BANK #{@computer.board.bank} AT $9000") if model.superpet?
252
+
166
253
  info("EXPANSION RAM", model.expansion ? "64K BOARD" : "")
167
254
  end
168
255
 
@@ -186,12 +273,15 @@ module Badline
186
273
  end
187
274
  end
188
275
 
189
- # The C128 names its model and the mode it runs in first, the Plus/4
190
- # and the PET their model and RAM, and the Plus/4 its RAM expansion if
191
- # it has one.
276
+ # The C128 names its model and the mode it runs in first, the Plus/4,
277
+ # the PET and the CBM-II their model and RAM, the Plus/4 its RAM
278
+ # expansion if it has one, and the CBM-II its KERNAL, the B series'
279
+ # 901244 or the P500's 901234.
192
280
  def draw_power
193
281
  info("MACHINE", "#{@computer.model.name.upcase}, #{@computer.mode.to_s.upcase} MODE") if c128?
194
- info("MACHINE", @computer.model_label) if pet?
282
+ info("MACHINE", @computer.model_label.upcase) if pet? || cbm2? || kim1?
283
+ info("KERNAL", "#{Controls.p500?(@computer) ? 901_234 : 901_244}-#{@computer.kernal.upcase}") if cbm2?
284
+ draw_superpet_switches if pet? && @computer.superpet?
195
285
  if plus4?
196
286
  info("MACHINE", @computer.model_label)
197
287
  expansion = @computer.ram_expansion_label
@@ -203,6 +293,25 @@ module Badline
203
293
  row("QUIT", :quit)
204
294
  end
205
295
 
296
+ # The SuperPET's CPU switch, which a move applies with a reset, and its
297
+ # bank RAM switch, which applies at once, with the CPU that has the
298
+ # bus.
299
+ def draw_superpet_switches
300
+ board = @computer.board
301
+ toggle("CPU SWITCH", :cpu_switch, [*CPU_SWITCH, PET::SuperPET::Board::CPU_SWITCH.index(board.cpu_switch)])
302
+ toggle("RAM SWITCH", :ram_switch, [*RAM_SWITCH, PET::SuperPET::Board::RAM_SWITCH.index(board.ram_switch)])
303
+ info("RUNNING", @computer.m6809_running? ? "6809, WATERLOO" : "6502, BASIC 4")
304
+ end
305
+
306
+ def switch_cpu(action)
307
+ position = PET::SuperPET::Board::CPU_SWITCH[CPU_SWITCH[1].index(action)]
308
+ @computer.switch_cpu(position) unless position == @computer.board.cpu_switch
309
+ end
310
+
311
+ def switch_bank_ram(action)
312
+ @computer.switch_bank_ram(PET::SuperPET::Board::RAM_SWITCH[RAM_SWITCH[1].index(action)])
313
+ end
314
+
206
315
  # A label, and its value under it, or NONE in the dim colour when the
207
316
  # value is empty.
208
317
  def info(label, value)
@@ -6,8 +6,13 @@ module Badline
6
6
  # control port has plugged in, a joystick or a pot device, and whether
7
7
  # the keys type or drive the joysticks. The VIC-20 has one port, a
8
8
  # joystick's, and the Plus/4 two joysticks and no pot devices. The PET
9
- # has no ports, and the page, KEYBOARD there, says where its keys are
10
- # instead.
9
+ # and the CBM-II's B series have no ports, and the page, KEYBOARD
10
+ # there, says where their keys are instead. The P500 has two joysticks,
11
+ # and the page says where its keys are while they type. The KIM-1's,
12
+ # PANEL there, sets its SST switch and TTY/KB jumper, which presses RS
13
+ # for the monitor to start again on the keypad or the teletype, and what its
14
+ # application connector wires: U3's PB7 to IRQ, and a speaker on U3's
15
+ # PA0 in place of the audio output on U2's PB7.
11
16
  module MenuPorts
12
17
  PORT_DEVICES = %i[joystick mouse paddles].freeze
13
18
  PORT_NAMES = %w[JOY MOUSE PADDLES].freeze
@@ -21,15 +26,22 @@ module Badline
21
26
  PET_GRAPHICS_KEYS = ["ESC: RUN/STOP", "SHIFT+LETTER: GRAPHIC", "ALT+SYMBOL: GRAPHIC"].freeze
22
27
  PET_BUSINESS_KEYS = ["ESC: ESC", "TAB: TAB", "PAGE UP: REPEAT", "KEYPAD: KEYPAD", "ALT+KEY: SHIFT+KEY"].freeze
23
28
 
29
+ # Where the CBM-II's keys without a character of their own are.
30
+ CBM2_KEYS = ["ESC: ESC", "F12: RUN/STOP", "HOME: CLR/HOME", "BACKSPACE: INST/DEL", "F1-F8: F1-F8",
31
+ "ALT+F9, ALT+F10: F9, F10", "KEYPAD: KEYPAD", "ALT+0: KEYPAD 00", "ALT+C: C= ALT+E: CE",
32
+ "ALT+G: NORM/GRAPH", "ALT+R: OFF/RVS ALT+P: PI", "_: LEFT ARROW"].freeze
33
+
24
34
  private
25
35
 
26
36
  def draw_ports
27
37
  return draw_vic20_port if @computer.family == :vic20
28
38
  return draw_plus4_ports if @computer.family == :plus4
29
39
  return draw_pet_keys if @computer.family == :pet
40
+ return draw_p500_ports if Controls.p500?(@computer)
41
+ return draw_cbm2_keys if @computer.family == :cbm2
42
+ return draw_kim1_panel if @computer.family == :kim1
30
43
 
31
- toggle("PORT 1", :port1, [PORT_NAMES, PORT_ACTIONS[0], PORT_DEVICES.index(port_device(1))])
32
- toggle("PORT 2", :port2, [PORT_NAMES, PORT_ACTIONS[1], PORT_DEVICES.index(port_device(2))])
44
+ draw_pot_ports
33
45
  return unless draw_keys(@computer.family == :c128 ? "C128" : "C64")
34
46
 
35
47
  row("SWAP JOYSTICKS", :swap)
@@ -61,15 +73,76 @@ module Badline
61
73
 
62
74
  # The PET's keyboard, whose keys type the characters on the host's
63
75
  # (PETKeyboard), and the keys that stand for the rest of its keys.
76
+ # The SuperPET's 6809 shows whether its keys type ASCII, or go by
77
+ # position for the APL symbols on its keycaps.
64
78
  def draw_pet_keys
65
79
  business = @computer.model.keyboard == :business
66
80
  info("KEYBOARD", business ? "BUSINESS" : "GRAPHICS")
81
+ draw_superpet_characters if @computer.superpet?
67
82
  (PET_KEYS + (business ? PET_BUSINESS_KEYS : PET_GRAPHICS_KEYS)).each do |line|
68
83
  @painter.text(@left, @top, line, MenuTheme::TEXT)
69
84
  @top += MenuPages::LINE
70
85
  end
71
86
  end
72
87
 
88
+ def draw_superpet_characters
89
+ apl = @computer.apl_characters?
90
+ return unless apl || @computer.m6809_running?
91
+
92
+ info("CHARACTERS", apl ? "APL, KEYS BY POSITION" : "ASCII")
93
+ end
94
+
95
+ def draw_kim1_panel
96
+ kim = @computer
97
+ toggle("SST", :sst, [%w[OFF ON], %i[sst_off sst_on], kim.sst ? 1 : 0])
98
+ toggle("TTY/KB", :tty, [%w[KEYPAD TTY], %i[keypad teletype], kim.tty? ? 1 : 0])
99
+ skip
100
+ info("RAM", "#{kim.ram_kb}K")
101
+ toggle("U3 PB7", :irq, [%w[OPEN IRQ], %i[irq_open irq_wired], kim.irq_wired ? 1 : 0])
102
+ toggle("SOUND", :speaker, [["U2 PB7", "U3 PA0"], %i[pb7_out pa0_speaker], kim.speaker? ? 1 : 0])
103
+ end
104
+
105
+ def kim1_setting(action)
106
+ kim = @computer
107
+ case action
108
+ when :sst_off, :sst_on then kim.sst = action == :sst_on
109
+ when :keypad, :teletype
110
+ kim.tty = action == :teletype
111
+ kim.reset!
112
+ when :irq_open, :irq_wired then kim.irq_wired = action == :irq_wired
113
+ else kim.speaker = action == :pa0_speaker
114
+ end
115
+ end
116
+
117
+ # What each control port has plugged in, a joystick, a mouse or
118
+ # paddles.
119
+ def draw_pot_ports
120
+ toggle("PORT 1", :port1, [PORT_NAMES, PORT_ACTIONS[0], PORT_DEVICES.index(port_device(1))])
121
+ toggle("PORT 2", :port2, [PORT_NAMES, PORT_ACTIONS[1], PORT_DEVICES.index(port_device(2))])
122
+ end
123
+
124
+ # The P500's two ports, its joysticks one for each set of keys, and
125
+ # while the keys type, where its keys are.
126
+ def draw_p500_ports
127
+ draw_pot_ports
128
+ return draw_cbm2_keys unless draw_keys("P500")
129
+
130
+ row("SWAP JOYSTICKS", :swap)
131
+ skip
132
+ arrows = @controls.arrows_port
133
+ info("ARROWS, SPACE", "JOYSTICK #{arrows}")
134
+ info("WASD, LEFT SHIFT", "JOYSTICK #{3 - arrows}")
135
+ end
136
+
137
+ # The CBM-II's keyboard, which types as the PET's does, and the keys
138
+ # that stand for the rest of its keys.
139
+ def draw_cbm2_keys
140
+ CBM2_KEYS.each do |line|
141
+ @painter.text(@left, @top, line, MenuTheme::TEXT)
142
+ @top += MenuPages::LINE
143
+ end
144
+ end
145
+
73
146
  # The toggle between the keyboard named and the joysticks, and
74
147
  # whether the keys drive the joysticks.
75
148
  def draw_keys(keyboard)
@@ -88,7 +88,7 @@ module Badline
88
88
  # any: :resume, :quit, :freeze or :swap.
89
89
  def click(left, top)
90
90
  action = @buttons.action_at(left, top)
91
- return @dialogs.click(action, top) if %i[browse confirm cancel].include?(action)
91
+ return @dialogs.click(action, top) if %i[browse confirm cancel].include?(action) || picks_drive?(action)
92
92
 
93
93
  choose(action)
94
94
  end
@@ -159,6 +159,7 @@ module Badline
159
159
  when :insert_cartridge then @dialogs.browse(:cartridge)
160
160
  when :quick_open then @dialogs.browse(:program)
161
161
  when :remove_cartridge then @dialogs.ask_remove
162
+ when :drive_type then @dialogs.pick_drive(@pages.drive_model)
162
163
  else @pages.perform(action)
163
164
  end
164
165
  nil
@@ -235,10 +236,13 @@ module Badline
235
236
  repeat.zero? || Keys::DIRECTIONS.key?(scancode) ? key(scancode) : nil
236
237
  end
237
238
 
239
+ def picks_drive?(action) = DriveTypes::ACTIONS.include?(action) && @dialogs.open?
240
+
238
241
  def draw_sections
239
242
  top = @top + 32
240
243
  SECTIONS.each_with_index do |name, index|
241
- name = "KEYBOARD" if PAGES[index] == :ports && @pages.pet?
244
+ name = "KEYBOARD" if PAGES[index] == :ports && (@pages.pet? || @pages.b_series?)
245
+ name = "PANEL" if PAGES[index] == :ports && @pages.kim1?
242
246
  @buttons.plain(@left + 6, top, name.ljust(14), PAGES[index], on: index == @section)
243
247
  top += Buttons::HEIGHT + 2
244
248
  end