// // MachinePicker.swift // Clock Signal // // Created by Thomas Harte on 02/04/2018. // Copyright 2018 Thomas Harte. All rights reserved. // import Cocoa // Quick note on structure below: I have an NSTableView to contain machine names, // programmatically manipulating the selected tab in an NSTabView. I admit that this // is odd. It's partly for historical reasons — this was purely an NSTabView until // the number of options got too large — and partly because it makes designing things // in the interface builder easier. // // I accept that I'll have to rethink this again if the machine list keeps growing. class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate { @IBOutlet var machineSelector: NSTabView! @IBOutlet var machineNameTable: NSTableView! // MARK: - Amiga properties @IBOutlet var amigaChipRAMButton: NSPopUpButton! @IBOutlet var amigaFastRAMButton: NSPopUpButton! // MARK: - Apple II properties @IBOutlet var appleIIModelButton: NSPopUpButton! @IBOutlet var appleIIDiskControllerButton: NSPopUpButton! // MARK: - Apple IIgs properties @IBOutlet var appleIIgsModelButton: NSPopUpButton! @IBOutlet var appleIIgsMemorySizeButton: NSPopUpButton! // MARK: - Atari ST properties @IBOutlet var atariSTMemorySizeButton: NSPopUpButton! // MARK: - CPC properties @IBOutlet var cpcModelTypeButton: NSPopUpButton! // MARK: - Electron properties @IBOutlet var electronDFSButton: NSButton! @IBOutlet var electronADFSButton: NSButton! @IBOutlet var electronAP6Button: NSButton! @IBOutlet var electronSidewaysRAMButton: NSButton! // MARK: - Enterprise properties @IBOutlet var enterpriseModelButton: NSPopUpButton! @IBOutlet var enterpriseSpeedButton: NSPopUpButton! @IBOutlet var enterpriseEXOSButton: NSPopUpButton! @IBOutlet var enterpriseBASICButton: NSPopUpButton! @IBOutlet var enterpriseDOSButton: NSPopUpButton! // MARK: - Macintosh properties @IBOutlet var macintoshModelTypeButton: NSPopUpButton! // MARK: - MSX properties @IBOutlet var msxModelButton: NSPopUpButton! @IBOutlet var msxRegionButton: NSPopUpButton! @IBOutlet var msxHasDiskDriveButton: NSButton! @IBOutlet var msxHasMSXMUSICButton: NSButton! // MARK: - Oric properties @IBOutlet var oricModelTypeButton: NSPopUpButton! @IBOutlet var oricDiskInterfaceButton: NSPopUpButton! // MARK: - Spectrum properties @IBOutlet var spectrumModelTypeButton: NSPopUpButton! // MARK: - Vic-20 properties @IBOutlet var vic20RegionButton: NSPopUpButton! @IBOutlet var vic20MemorySizeButton: NSPopUpButton! @IBOutlet var vic20HasC1540Button: NSButton! // MARK: - ZX80 properties @IBOutlet var zx80MemorySizeButton: NSPopUpButton! @IBOutlet var zx80UsesZX81ROMButton: NSButton! // MARK: - ZX81 properties @IBOutlet var zx81MemorySizeButton: NSPopUpButton! // MARK: - Preferences func establishStoredOptions() { let standardUserDefaults = UserDefaults.standard // Set up data soure. // TEMPORARY: remove the Apple IIgs and PC compatible options. // Neither is yet a fully-working machine. #if !DEBUG for hidden in ["appleiigs", "pc"] { let tabIndex = machineSelector.indexOfTabViewItem(withIdentifier: hidden) machineSelector.removeTabViewItem(machineSelector.tabViewItem(at: tabIndex)) } machineNameTable.reloadData() #endif // Machine type if let machineIdentifier = standardUserDefaults.string(forKey: "new.machine") { // If I've changed my mind about visible tabs between versions, there may not be one that corresponds // to the stored identifier. Make sure not to raise an NSRangeException in that scenario. let index = machineSelector.indexOfTabViewItem(withIdentifier: machineIdentifier as Any) if index != NSNotFound { machineSelector.selectTabViewItem(at: index) machineNameTable.selectRowIndexes(IndexSet(integer: index), byExtendingSelection: false) machineNameTable.scrollRowToVisible(index) } } // Amiga settings amigaChipRAMButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.amigaChipRAM")) amigaFastRAMButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.amigaFastRAM")) // Apple II settings appleIIModelButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.appleIIModel")) appleIIDiskControllerButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.appleIIDiskController")) // Apple IIgs settings appleIIgsModelButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.appleIIgsModel")) appleIIgsMemorySizeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.appleIIgsMemorySize")) // Atari ST settings atariSTMemorySizeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.atariSTMemorySize")) // CPC settings cpcModelTypeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.cpcModel")) // Electron settings electronDFSButton.state = standardUserDefaults.bool(forKey: "new.electronDFS") ? .on : .off electronADFSButton.state = standardUserDefaults.bool(forKey: "new.electronADFS") ? .on : .off electronAP6Button.state = standardUserDefaults.bool(forKey: "new.electronAP6") ? .on : .off electronSidewaysRAMButton.state = standardUserDefaults.bool(forKey: "new.electronSidewaysRAM") ? .on : .off // Enterprise settings enterpriseModelButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseModel")) enterpriseSpeedButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseSpeed")) enterpriseEXOSButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseEXOSVersion")) enterpriseBASICButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseBASICVersion")) enterpriseDOSButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseDOS")) // Macintosh settings macintoshModelTypeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.macintoshModel")) // MSX settings msxModelButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.msxModel")) msxRegionButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.msxRegion")) msxHasDiskDriveButton.state = standardUserDefaults.bool(forKey: "new.msxDiskDrive") ? .on : .off msxHasMSXMUSICButton.state = standardUserDefaults.bool(forKey: "new.msxMSXMUSIC") ? .on : .off // Oric settings oricDiskInterfaceButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.oricDiskInterface")) oricModelTypeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.oricModel")) // Spectrum settings spectrumModelTypeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.spectrumModel")) // Vic-20 settings vic20RegionButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.vic20Region")) vic20MemorySizeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.vic20MemorySize")) vic20HasC1540Button.state = standardUserDefaults.bool(forKey: "new.vic20C1540") ? .on : .off // ZX80 zx80MemorySizeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.zx80MemorySize")) zx80UsesZX81ROMButton.state = standardUserDefaults.bool(forKey: "new.zx80UsesZX81ROM") ? .on : .off // ZX81 zx81MemorySizeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.zx81MemorySize")) } fileprivate func storeOptions() { let standardUserDefaults = UserDefaults.standard // Machine type standardUserDefaults.set(machineSelector.selectedTabViewItem!.identifier as! String, forKey: "new.machine") // Amiga settings standardUserDefaults.set(amigaChipRAMButton.selectedTag(), forKey: "new.amigaChipRAM") standardUserDefaults.set(amigaFastRAMButton.selectedTag(), forKey: "new.amigaFastRAM") // Apple II settings standardUserDefaults.set(appleIIModelButton.selectedTag(), forKey: "new.appleIIModel") standardUserDefaults.set(appleIIDiskControllerButton.selectedTag(), forKey: "new.appleIIDiskController") // Apple IIgs settings standardUserDefaults.set(appleIIgsModelButton.selectedTag(), forKey: "new.appleIIgsModel") standardUserDefaults.set(appleIIgsMemorySizeButton.selectedTag(), forKey: "new.appleIIgsMemorySize") // Atari ST settings standardUserDefaults.set(atariSTMemorySizeButton.selectedTag(), forKey: "new.atariSTMemorySize") // CPC settings standardUserDefaults.set(cpcModelTypeButton.selectedTag(), forKey: "new.cpcModel") // Electron settings standardUserDefaults.set(electronDFSButton.state == .on, forKey: "new.electronDFS") standardUserDefaults.set(electronADFSButton.state == .on, forKey: "new.electronADFS") standardUserDefaults.set(electronAP6Button.state == .on, forKey: "new.electronAP6") standardUserDefaults.set(electronSidewaysRAMButton.state == .on, forKey: "new.electronSidewaysRAM") // Enterprise settings standardUserDefaults.set(enterpriseModelButton.selectedTag(), forKey: "new.enterpriseModel") standardUserDefaults.set(enterpriseSpeedButton.selectedTag(), forKey: "new.enterpriseSpeed") standardUserDefaults.set(enterpriseEXOSButton.selectedTag(), forKey: "new.enterpriseEXOSVersion") standardUserDefaults.set(enterpriseBASICButton.selectedTag(), forKey: "new.enterpriseBASICVersion") standardUserDefaults.set(enterpriseDOSButton.selectedTag(), forKey: "new.enterpriseDOS") // Macintosh settings standardUserDefaults.set(macintoshModelTypeButton.selectedTag(), forKey: "new.macintoshModel") // MSX settings standardUserDefaults.set(msxModelButton.selectedTag(), forKey: "new.msxModel") standardUserDefaults.set(msxRegionButton.selectedTag(), forKey: "new.msxRegion") standardUserDefaults.set(msxHasDiskDriveButton.state == .on, forKey: "new.msxDiskDrive") standardUserDefaults.set(msxHasMSXMUSICButton.state == .on, forKey: "new.msxMSXMUSIC") // Oric settings standardUserDefaults.set(oricDiskInterfaceButton.selectedTag(), forKey: "new.oricDiskInterface") standardUserDefaults.set(oricModelTypeButton.selectedTag(), forKey: "new.oricModel") // Spectrum settings standardUserDefaults.set(spectrumModelTypeButton.selectedTag(), forKey: "new.spectrumModel") // Vic-20 settings standardUserDefaults.set(vic20RegionButton.selectedTag(), forKey: "new.vic20Region") standardUserDefaults.set(vic20MemorySizeButton.selectedTag(), forKey: "new.vic20MemorySize") standardUserDefaults.set(vic20HasC1540Button.state == .on, forKey: "new.vic20C1540") // ZX80 standardUserDefaults.set(zx80MemorySizeButton.selectedTag(), forKey: "new.zx80MemorySize") standardUserDefaults.set(zx80UsesZX81ROMButton.state == .on, forKey: "new.zx80UsesZX81ROM") // ZX81 standardUserDefaults.set(zx81MemorySizeButton.selectedTag(), forKey: "new.zx81MemorySize") } // MARK: - Table view handling func numberOfRows(in tableView: NSTableView) -> Int { return machineSelector.numberOfTabViewItems } func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { return machineSelector.tabViewItem(at: row).label } func tableViewSelectionDidChange(_ notification: Notification) { machineSelector.selectTabViewItem(at: machineNameTable.selectedRow) } func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { let font = NSFont.systemFont(ofSize: NSFont.systemFontSize) // YUCK. TODO: find a way to use cells with vertically-centred text. // Likely that means not using NSTextFieldCell. return font.ascender - font.descender + 2.5 } // MARK: - Machine builder func selectedMachine() -> CSStaticAnalyser { storeOptions() switch machineSelector.selectedTabViewItem!.identifier as! String { case "amiga": return CSStaticAnalyser(amigaModel: .A500, chipMemorySize: Kilobytes(amigaChipRAMButton.selectedTag()), fastMemorySize: Kilobytes(amigaFastRAMButton.selectedTag())) case "appleii": var model: CSMachineAppleIIModel = .appleII switch appleIIModelButton.selectedTag() { case 1: model = .appleIIPlus case 2: model = .appleIIe case 3: model = .appleEnhancedIIe case 0: fallthrough default: model = .appleII } var diskController: CSMachineAppleIIDiskController = .none switch appleIIDiskControllerButton.selectedTag() { case 13: diskController = .thirteenSector case 16: diskController = .sixteenSector case 0: fallthrough default: diskController = .none } return CSStaticAnalyser(appleIIModel: model, diskController: diskController) case "appleiigs": var model: CSMachineAppleIIgsModel = .ROM00 switch appleIIgsModelButton.selectedTag() { case 1: model = .ROM01 case 2: model = .ROM03 case 0: fallthrough default: model = .ROM00 } let memorySize = Kilobytes(appleIIgsMemorySizeButton.selectedTag()) return CSStaticAnalyser(appleIIgsModel: model, memorySize: memorySize) case "atarist": let memorySize = Kilobytes(atariSTMemorySizeButton.selectedTag()) return CSStaticAnalyser(atariSTModel: .model512k, memorySize: memorySize) case "cpc": switch cpcModelTypeButton.selectedTag() { case 464: return CSStaticAnalyser(amstradCPCModel: .model464) case 664: return CSStaticAnalyser(amstradCPCModel: .model664) case 6128: fallthrough default: return CSStaticAnalyser(amstradCPCModel: .model6128) } case "electron": return CSStaticAnalyser( electronDFS: electronDFSButton.state == .on, adfs: electronADFSButton.state == .on, ap6: electronAP6Button.state == .on, sidewaysRAM: electronSidewaysRAMButton.state == .on) case "enterprise": var model: CSMachineEnterpriseModel = .model128 switch enterpriseModelButton.selectedTag() { case 64: model = .model64 case 256: model = .model256 case 128: fallthrough default: model = .model128 } var speed: CSMachineEnterpriseSpeed = .speed4MHz switch enterpriseSpeedButton.selectedTag() { case 6: speed = .speed6MHz case 4: fallthrough default: speed = .speed4MHz } var exos: CSMachineEnterpriseEXOS = .version21 switch enterpriseEXOSButton.selectedTag() { case 10: exos = .version10 case 20: exos = .version20 case 21: fallthrough default: exos = .version21 } var basic: CSMachineEnterpriseBASIC = .version21 switch enterpriseBASICButton.selectedTag() { case 0: basic = .none case 10: basic = .version10 case 11: basic = .version11 case 21: fallthrough default: basic = .version21 } var dos: CSMachineEnterpriseDOS = .dosNone switch enterpriseDOSButton.selectedTag() { case 1: dos = .DOSEXDOS case 0: fallthrough default: dos = .dosNone } return CSStaticAnalyser(enterpriseModel: model, speed: speed, exosVersion: exos, basicVersion: basic, dos: dos) case "mac": switch macintoshModelTypeButton.selectedTag() { case 0: return CSStaticAnalyser(macintoshModel: .model128k) case 1: return CSStaticAnalyser(macintoshModel: .model512k) case 2: return CSStaticAnalyser(macintoshModel: .model512ke) case 3: fallthrough default: return CSStaticAnalyser(macintoshModel: .modelPlus) } case "msx": let hasDiskDrive = msxHasDiskDriveButton.state == .on let hasMSXMUSIC = msxHasMSXMUSICButton.state == .on var region: CSMachineMSXRegion switch msxRegionButton.selectedTag() { case 2: region = .japanese case 1: region = .american case 0: fallthrough default: region = .european } var model: CSMachineMSXModel switch msxModelButton.selectedTag() { case 2: model = .MSX2 case 1: fallthrough default: model = .MSX1 } return CSStaticAnalyser(msxModel: model, region: region, hasDiskDrive: hasDiskDrive, hasMSXMUSIC: hasMSXMUSIC) case "oric": var diskInterface: CSMachineOricDiskInterface = .none switch oricDiskInterfaceButton.selectedTag() { case 1: diskInterface = .microdisc case 2: diskInterface = .pravetz case 3: diskInterface = .jasmin case 4: diskInterface = .BD500 default: break } var model: CSMachineOricModel = .oric1 switch oricModelTypeButton.selectedTag() { case 1: model = .oricAtmos case 2: model = .pravetz default: break } return CSStaticAnalyser(oricModel: model, diskInterface: diskInterface) case "pc": return CSStaticAnalyser(pcCompatibleModel: .turboXT) case "spectrum": var model: CSMachineSpectrumModel = .plus2a switch spectrumModelTypeButton.selectedTag() { case 16: model = .sixteenK case 48: model = .fortyEightK case 128: model = .oneTwoEightK case 2: model = .plus2 case 21: model = .plus2a case 3: model = .plus3 default: break } return CSStaticAnalyser(spectrumModel: model) case "vic20": let memorySize = Kilobytes(vic20MemorySizeButton.selectedTag()) let hasC1540 = vic20HasC1540Button.state == .on switch vic20RegionButton.selectedTag() { case 1: return CSStaticAnalyser(vic20Region: .american, memorySize: memorySize, hasC1540: hasC1540) case 2: return CSStaticAnalyser(vic20Region: .danish, memorySize: memorySize, hasC1540: hasC1540) case 3: return CSStaticAnalyser(vic20Region: .swedish, memorySize: memorySize, hasC1540: hasC1540) case 4: return CSStaticAnalyser(vic20Region: .japanese, memorySize: memorySize, hasC1540: hasC1540) case 0: fallthrough default: return CSStaticAnalyser(vic20Region: .european, memorySize: memorySize, hasC1540: hasC1540) } case "zx80": return CSStaticAnalyser(zx80MemorySize: Kilobytes(zx80MemorySizeButton.selectedTag()), useZX81ROM: zx80UsesZX81ROMButton.state == .on) case "zx81": return CSStaticAnalyser(zx81MemorySize: Kilobytes(zx81MemorySizeButton.selectedTag())) default: return CSStaticAnalyser() } } }