mirror of
https://github.com/Luigi30/FruitMachine-Swift.git
synced 2024-11-30 03:57:21 +00:00
working on adding notifications, adding preferences
This commit is contained in:
parent
a9a12189af
commit
cafacf2f18
@ -114,6 +114,10 @@ class AppleIIBase: NSObject, EmulatedSystem {
|
||||
|
||||
func doColdReset() {
|
||||
CPU.sharedInstance.coldReset()
|
||||
|
||||
//Reinitialize peripherals in case they changed.
|
||||
setupPeripherals()
|
||||
|
||||
doReset()
|
||||
}
|
||||
|
||||
|
@ -62,8 +62,6 @@ extension AppleIIBase {
|
||||
}
|
||||
rowNumber += Int((offset / 0x80) * 8)
|
||||
|
||||
//if(pixel & 0x80)
|
||||
|
||||
let dot0 = (pixel & 0x01) == 0x01
|
||||
let dot1 = (pixel & 0x02) == 0x02
|
||||
let dot2 = (pixel & 0x04) == 0x04
|
||||
@ -80,48 +78,68 @@ extension AppleIIBase {
|
||||
}
|
||||
|
||||
if(dot0) {
|
||||
buffer![pixelRowOffset + 0 + pixelColumnOffset] = AppleII.LoresColors.White
|
||||
buffer![pixelRowOffset + 0 + pixelColumnOffset] = AppleIIBase.HiresMode.Colors.White
|
||||
} else {
|
||||
buffer![pixelRowOffset + 0 + pixelColumnOffset] = AppleII.LoresColors.Black
|
||||
buffer![pixelRowOffset + 0 + pixelColumnOffset] = AppleIIBase.HiresMode.Colors.Black
|
||||
}
|
||||
|
||||
if(dot1) {
|
||||
buffer![pixelRowOffset + 1 + pixelColumnOffset] = AppleII.LoresColors.White
|
||||
buffer![pixelRowOffset + 1 + pixelColumnOffset] = AppleIIBase.HiresMode.Colors.White
|
||||
} else {
|
||||
buffer![pixelRowOffset + 1 + pixelColumnOffset] = AppleII.LoresColors.Black
|
||||
buffer![pixelRowOffset + 1 + pixelColumnOffset] = AppleIIBase.HiresMode.Colors.Black
|
||||
}
|
||||
|
||||
if(dot2) {
|
||||
buffer![pixelRowOffset + 2 + pixelColumnOffset] = AppleII.LoresColors.White
|
||||
buffer![pixelRowOffset + 2 + pixelColumnOffset] = AppleIIBase.HiresMode.Colors.White
|
||||
} else {
|
||||
buffer![pixelRowOffset + 2 + pixelColumnOffset] = AppleII.LoresColors.Black
|
||||
buffer![pixelRowOffset + 2 + pixelColumnOffset] = AppleIIBase.HiresMode.Colors.Black
|
||||
}
|
||||
|
||||
if(dot3) {
|
||||
buffer![pixelRowOffset + 3 + pixelColumnOffset] = AppleII.LoresColors.White
|
||||
buffer![pixelRowOffset + 3 + pixelColumnOffset] = AppleIIBase.HiresMode.Colors.White
|
||||
} else {
|
||||
buffer![pixelRowOffset + 3 + pixelColumnOffset] = AppleII.LoresColors.Black
|
||||
buffer![pixelRowOffset + 3 + pixelColumnOffset] = AppleIIBase.HiresMode.Colors.Black
|
||||
}
|
||||
|
||||
if(dot4) {
|
||||
buffer![pixelRowOffset + 4 + pixelColumnOffset] = AppleII.LoresColors.White
|
||||
buffer![pixelRowOffset + 4 + pixelColumnOffset] = AppleIIBase.HiresMode.Colors.White
|
||||
} else {
|
||||
buffer![pixelRowOffset + 4 + pixelColumnOffset] = AppleII.LoresColors.Black
|
||||
buffer![pixelRowOffset + 4 + pixelColumnOffset] = AppleIIBase.HiresMode.Colors.Black
|
||||
}
|
||||
|
||||
if(dot5) {
|
||||
buffer![pixelRowOffset + 5 + pixelColumnOffset] = AppleII.LoresColors.White
|
||||
buffer![pixelRowOffset + 5 + pixelColumnOffset] = AppleIIBase.HiresMode.Colors.White
|
||||
} else {
|
||||
buffer![pixelRowOffset + 5 + pixelColumnOffset] = AppleII.LoresColors.Black
|
||||
buffer![pixelRowOffset + 5 + pixelColumnOffset] = AppleIIBase.HiresMode.Colors.Black
|
||||
}
|
||||
|
||||
if(dot6) {
|
||||
buffer![pixelRowOffset + 6 + pixelColumnOffset] = AppleII.LoresColors.White
|
||||
buffer![pixelRowOffset + 6 + pixelColumnOffset] = AppleIIBase.HiresMode.Colors.White
|
||||
} else {
|
||||
buffer![pixelRowOffset + 6 + pixelColumnOffset] = AppleII.LoresColors.Black
|
||||
buffer![pixelRowOffset + 6 + pixelColumnOffset] = AppleIIBase.HiresMode.Colors.Black
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
struct Colors {
|
||||
static let Black = BitmapPixelsLE555.RGB32toLE555(r: 0, g: 0, b: 0)
|
||||
static let White = BitmapPixelsLE555.RGB32toLE555(r: 255, g: 255, b: 255)
|
||||
|
||||
static func getColor(index: UInt8) -> BitmapPixelsLE555.PixelData {
|
||||
switch index {
|
||||
case 0: return AppleIIBase.HiresMode.Colors.Black
|
||||
case 1: return AppleIIBase.HiresMode.Colors.White
|
||||
case 2: return AppleIIBase.HiresMode.Colors.White
|
||||
case 3: return AppleIIBase.HiresMode.Colors.White
|
||||
case 4: return AppleIIBase.HiresMode.Colors.White
|
||||
case 5: return AppleIIBase.HiresMode.Colors.White
|
||||
case 6: return AppleIIBase.HiresMode.Colors.White
|
||||
case 7: return AppleIIBase.HiresMode.Colors.White
|
||||
default:
|
||||
print("tried to get color > 15")
|
||||
return AppleIIBase.HiresMode.Colors.Black
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,8 +22,8 @@ extension AppleIIBase {
|
||||
let pixelNybbleHi = pixel & 0x0F
|
||||
let pixelNybbleLo = (pixel & 0xF0) >> 4
|
||||
|
||||
let colorHi = AppleII.LoresColors.getColor(index: pixelNybbleHi)
|
||||
let colorLo = LoresColors.getColor(index: pixelNybbleLo)
|
||||
let colorHi = AppleIIBase.LoresMode.Colors.getColor(index: pixelNybbleHi)
|
||||
let colorLo = AppleIIBase.LoresMode.Colors.getColor(index: pixelNybbleLo)
|
||||
|
||||
//One lores pixel is 7px wide and 4px tall for a resolution of 40x48.
|
||||
let baseOffset = EmulatedSystemInstance!.emulatorViewDelegate.scanlineOffsets[Int(pixelPosition.y)] + Int(pixelPosition.x)
|
||||
@ -44,6 +44,50 @@ extension AppleIIBase {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct Colors {
|
||||
static let Black = BitmapPixelsLE555.RGB32toLE555(r: 0, g: 0, b: 0)
|
||||
static let Magenta = BitmapPixelsLE555.RGB32toLE555(r: 227, g: 30, b: 96)
|
||||
static let DarkBlue = BitmapPixelsLE555.RGB32toLE555(r: 96, g: 78, b: 189)
|
||||
static let Purple = BitmapPixelsLE555.RGB32toLE555(r: 255, g: 68, b: 253)
|
||||
static let DarkGreen = BitmapPixelsLE555.RGB32toLE555(r: 0, g: 163, b: 96)
|
||||
static let Gray1 = BitmapPixelsLE555.RGB32toLE555(r: 156, g: 156, b: 156)
|
||||
static let MediumBlue = BitmapPixelsLE555.RGB32toLE555(r: 20, g: 207, b: 253)
|
||||
static let LightBlue = BitmapPixelsLE555.RGB32toLE555(r: 208, g: 195, b: 255)
|
||||
static let Brown = BitmapPixelsLE555.RGB32toLE555(r: 96, g: 114, b: 3)
|
||||
static let Orange = BitmapPixelsLE555.RGB32toLE555(r: 255, g: 106, b: 60)
|
||||
static let Gray2 = BitmapPixelsLE555.RGB32toLE555(r: 156, g: 156, b: 156)
|
||||
static let Pink = BitmapPixelsLE555.RGB32toLE555(r: 255, g: 160, b: 208)
|
||||
static let LightGreen = BitmapPixelsLE555.RGB32toLE555(r: 20, g: 245, b: 60)
|
||||
static let Yellow = BitmapPixelsLE555.RGB32toLE555(r: 208, g: 221, b: 141)
|
||||
static let Aquamarine = BitmapPixelsLE555.RGB32toLE555(r: 114, g: 255, b: 208)
|
||||
static let White = BitmapPixelsLE555.RGB32toLE555(r: 255, g: 255, b: 255)
|
||||
|
||||
static func getColor(index: UInt8) -> BitmapPixelsLE555.PixelData {
|
||||
switch index {
|
||||
case 0: return AppleIIBase.LoresMode.Colors.Black
|
||||
case 1: return AppleIIBase.LoresMode.Colors.Magenta
|
||||
case 2: return AppleIIBase.LoresMode.Colors.DarkBlue
|
||||
case 3: return AppleIIBase.LoresMode.Colors.Purple
|
||||
case 4: return AppleIIBase.LoresMode.Colors.DarkGreen
|
||||
case 5: return AppleIIBase.LoresMode.Colors.Gray1
|
||||
case 6: return AppleIIBase.LoresMode.Colors.MediumBlue
|
||||
case 7: return AppleIIBase.LoresMode.Colors.LightBlue
|
||||
case 8: return AppleIIBase.LoresMode.Colors.Brown
|
||||
case 9: return AppleIIBase.LoresMode.Colors.Orange
|
||||
case 10: return AppleIIBase.LoresMode.Colors.Gray2
|
||||
case 11: return AppleIIBase.LoresMode.Colors.Pink
|
||||
case 12: return AppleIIBase.LoresMode.Colors.LightGreen
|
||||
case 13: return AppleIIBase.LoresMode.Colors.Yellow
|
||||
case 14: return AppleIIBase.LoresMode.Colors.Aquamarine
|
||||
case 15: return AppleIIBase.LoresMode.Colors.White
|
||||
default:
|
||||
print("tried to get color > 15")
|
||||
return AppleIIBase.LoresMode.Colors.Black
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -37,50 +37,6 @@ extension AppleIIBase {
|
||||
case MixedHires
|
||||
}
|
||||
|
||||
struct LoresColors {
|
||||
static let Black = BitmapPixelsLE555.RGB32toLE555(r: 0, g: 0, b: 0)
|
||||
static let Magenta = BitmapPixelsLE555.RGB32toLE555(r: 227, g: 30, b: 96)
|
||||
static let DarkBlue = BitmapPixelsLE555.RGB32toLE555(r: 96, g: 78, b: 189)
|
||||
static let Purple = BitmapPixelsLE555.RGB32toLE555(r: 255, g: 68, b: 253)
|
||||
static let DarkGreen = BitmapPixelsLE555.RGB32toLE555(r: 0, g: 163, b: 96)
|
||||
static let Gray1 = BitmapPixelsLE555.RGB32toLE555(r: 156, g: 156, b: 156)
|
||||
static let MediumBlue = BitmapPixelsLE555.RGB32toLE555(r: 20, g: 207, b: 253)
|
||||
static let LightBlue = BitmapPixelsLE555.RGB32toLE555(r: 208, g: 195, b: 255)
|
||||
static let Brown = BitmapPixelsLE555.RGB32toLE555(r: 96, g: 114, b: 3)
|
||||
static let Orange = BitmapPixelsLE555.RGB32toLE555(r: 255, g: 106, b: 60)
|
||||
static let Gray2 = BitmapPixelsLE555.RGB32toLE555(r: 156, g: 156, b: 156)
|
||||
static let Pink = BitmapPixelsLE555.RGB32toLE555(r: 255, g: 160, b: 208)
|
||||
static let LightGreen = BitmapPixelsLE555.RGB32toLE555(r: 20, g: 245, b: 60)
|
||||
static let Yellow = BitmapPixelsLE555.RGB32toLE555(r: 208, g: 221, b: 141)
|
||||
static let Aquamarine = BitmapPixelsLE555.RGB32toLE555(r: 114, g: 255, b: 208)
|
||||
static let White = BitmapPixelsLE555.RGB32toLE555(r: 255, g: 255, b: 255)
|
||||
|
||||
static func getColor(index: UInt8) -> BitmapPixelsLE555.PixelData {
|
||||
switch index {
|
||||
case 0: return AppleII.LoresColors.Black
|
||||
case 1: return AppleII.LoresColors.Magenta
|
||||
case 2: return AppleII.LoresColors.DarkBlue
|
||||
case 3: return AppleII.LoresColors.Purple
|
||||
case 4: return AppleII.LoresColors.DarkGreen
|
||||
case 5: return AppleII.LoresColors.Gray1
|
||||
case 6: return AppleII.LoresColors.MediumBlue
|
||||
case 7: return AppleII.LoresColors.LightBlue
|
||||
case 8: return AppleII.LoresColors.Brown
|
||||
case 9: return AppleII.LoresColors.Orange
|
||||
case 10: return AppleII.LoresColors.Gray2
|
||||
case 11: return AppleII.LoresColors.Pink
|
||||
case 12: return AppleII.LoresColors.LightGreen
|
||||
case 13: return AppleII.LoresColors.Yellow
|
||||
case 14: return AppleII.LoresColors.Aquamarine
|
||||
case 15: return AppleII.LoresColors.White
|
||||
default:
|
||||
print("tried to get color > 15")
|
||||
return AppleII.LoresColors.Black
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func getCurrentVideoMode(switches: VideoSoftswitches) -> VideoMode {
|
||||
if(switches.TEXT_MODE == true)
|
||||
{
|
||||
|
@ -8,6 +8,11 @@
|
||||
|
||||
import Cocoa
|
||||
|
||||
class EmulationNotifications {
|
||||
static let StartEmulation = Notification.Name("StartEmulation")
|
||||
static let StopEmulation = Notification.Name("StopEmulation")
|
||||
}
|
||||
|
||||
class AppleIIViewController: NSViewController {
|
||||
@IBOutlet weak var lbl_Drive1: NSTextField!
|
||||
@IBOutlet weak var lbl_Drive2: NSTextField!
|
||||
@ -21,20 +26,34 @@ class AppleIIViewController: NSViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
// Do view setup here.
|
||||
EmulatedSystemInstance = AppleIIPlus.sharedInstance
|
||||
|
||||
preferencesWindowController = PreferencesWindowController()
|
||||
setModel()
|
||||
|
||||
self.view.addSubview(EmulatedSystemInstance!.emulatorView)
|
||||
|
||||
preferencesWindowController.setupDefaultsIfRequired()
|
||||
setupDriveNotifications()
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(self.debuggerBreak), name: DebuggerNotifications.Break, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(self.stopFrameTimer), name: EmulationNotifications.StopEmulation, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(self.startFrameTimer), name: EmulationNotifications.StartEmulation, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(self.breakpointHit), name: CPUNotifications.BreakpointHit, object: nil)
|
||||
startFrameTimer()
|
||||
}
|
||||
|
||||
self.frameTimer = Timer.scheduledTimer(timeInterval: 1.0/60.0,
|
||||
target: self,
|
||||
selector: #selector(runEmulation),
|
||||
userInfo: nil,
|
||||
repeats: true)
|
||||
func setModel() {
|
||||
let model = UserDefaults.standard.string(forKey: "a2_Model")
|
||||
if (model == "Apple ][ (Original") {
|
||||
EmulatedSystemInstance = AppleII.sharedInstance
|
||||
} else if(model == "Apple ][+") {
|
||||
EmulatedSystemInstance = AppleIIPlus.sharedInstance
|
||||
} else {
|
||||
/* ??? */
|
||||
EmulatedSystemInstance = AppleII.sharedInstance
|
||||
}
|
||||
}
|
||||
|
||||
@objc func breakpointHit() {
|
||||
stopFrameTimer()
|
||||
showDebugger(self)
|
||||
}
|
||||
|
||||
@objc func runEmulation() {
|
||||
@ -44,20 +63,30 @@ class AppleIIViewController: NSViewController {
|
||||
}
|
||||
}
|
||||
|
||||
@objc func debuggerBreak() {
|
||||
frameTimer?.invalidate()
|
||||
CPU.sharedInstance.isRunning = false
|
||||
@objc func stopFrameTimer() {
|
||||
self.frameTimer?.invalidate()
|
||||
}
|
||||
|
||||
@objc func startFrameTimer() {
|
||||
self.frameTimer = Timer.scheduledTimer(timeInterval: 1.0/60.0,
|
||||
target: self,
|
||||
selector: #selector(runEmulation),
|
||||
userInfo: nil,
|
||||
repeats: true)
|
||||
}
|
||||
|
||||
@IBAction func showDebugger(_ sender: Any) {
|
||||
stopFrameTimer()
|
||||
let debuggerStoryboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Debugger"), bundle: nil)
|
||||
debuggerWindowController = debuggerStoryboard.instantiateInitialController() as! DebuggerWindowController
|
||||
debuggerWindowController.showWindow(self)
|
||||
}
|
||||
|
||||
@IBAction func showPreferences(_ sender: Any) {
|
||||
stopFrameTimer()
|
||||
preferencesWindowController.loadWindow()
|
||||
preferencesWindowController.showWindow(self)
|
||||
preferencesWindowController.setupPreferences()
|
||||
}
|
||||
|
||||
@IBAction func doReset(_ sender: Any) {
|
||||
@ -65,6 +94,7 @@ class AppleIIViewController: NSViewController {
|
||||
}
|
||||
|
||||
@IBAction func doColdReset(_ sender: Any) {
|
||||
setModel() //In case it changed
|
||||
EmulatedSystemInstance!.doColdReset()
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,10 @@ enum CPUModel {
|
||||
case M65C02
|
||||
}
|
||||
|
||||
class CPUNotifications {
|
||||
static let BreakpointHit = Notification.Name("BreakpointHit")
|
||||
}
|
||||
|
||||
struct StatusRegister {
|
||||
var negative: Bool //N - 0x80
|
||||
var overflow: Bool //V - 0x40
|
||||
@ -253,6 +257,7 @@ final class CPU: NSObject {
|
||||
}
|
||||
|
||||
final func executeNextInstruction() throws {
|
||||
|
||||
instruction_register = memoryInterface.readByte(offset: program_counter)
|
||||
let operation = InstructionTable[instruction_register]
|
||||
if(operation == nil) {
|
||||
@ -301,6 +306,11 @@ final class CPU: NSObject {
|
||||
/* Running */
|
||||
final func cpuStep() {
|
||||
do {
|
||||
if(breakpoints.contains(program_counter)) {
|
||||
isRunning = false
|
||||
NotificationCenter.default.post(name: CPUNotifications.BreakpointHit, object: nil)
|
||||
return
|
||||
}
|
||||
try executeNextInstruction()
|
||||
} catch CPUExceptions.invalidInstruction {
|
||||
print("Invalid instruction at \(program_counter.asHexString())")
|
||||
@ -313,13 +323,8 @@ final class CPU: NSObject {
|
||||
func runCyclesBatch() {
|
||||
isRunning = true
|
||||
|
||||
while(!outOfCycles()) {
|
||||
while(!outOfCycles() && isRunning) {
|
||||
cpuStep()
|
||||
|
||||
if (breakpoints.contains(program_counter)) {
|
||||
isRunning = false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -360,6 +360,9 @@
|
||||
</subviews>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="btn_Break" destination="94S-pW-GeW" id="aal-zp-5kg"/>
|
||||
<outlet property="btn_Run" destination="oRf-MK-DEX" id="Dv1-Tg-Ht7"/>
|
||||
<outlet property="btn_Step" destination="HdY-Jc-lNK" id="uvQ-eY-ika"/>
|
||||
<outlet property="debuggerTableView" destination="MuT-J3-VZ6" id="TMx-Do-29u"/>
|
||||
<outlet property="text_CPU_A" destination="WjO-ue-iRg" id="kaI-Bh-Efs"/>
|
||||
<outlet property="text_CPU_Flags" destination="ecN-Ge-1hE" id="dDw-IG-xNd"/>
|
||||
|
@ -25,6 +25,10 @@ class DebuggerViewController: NSViewController {
|
||||
|
||||
@IBOutlet weak var debuggerTableView: NSTableView!
|
||||
|
||||
@IBOutlet weak var btn_Break: NSButton!
|
||||
@IBOutlet weak var btn_Step: NSButton!
|
||||
@IBOutlet weak var btn_Run: NSButton!
|
||||
|
||||
var cpuInstance = CPU.sharedInstance
|
||||
var isRunning = false
|
||||
|
||||
@ -74,24 +78,6 @@ class DebuggerViewController: NSViewController {
|
||||
}
|
||||
}
|
||||
|
||||
func debugRun() {
|
||||
isRunning = true
|
||||
|
||||
cpuInstance.cycles = 0
|
||||
cpuInstance.cyclesInBatch = 10000
|
||||
|
||||
while(!cpuInstance.outOfCycles() && isRunning) {
|
||||
cpuInstance.cpuStep()
|
||||
|
||||
if (cpuInstance.breakpoints.contains(cpuInstance.program_counter)) {
|
||||
isRunning = false
|
||||
updateCPUStatusFields()
|
||||
debugConsolePrint(str: "Breakpoint reached at $\(cpuInstance.program_counter.asHexString())", newline: true)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func queueCPUStep(queue: DispatchQueue) {
|
||||
queue.async {
|
||||
self.cpuInstance.cpuStep()
|
||||
@ -105,17 +91,18 @@ class DebuggerViewController: NSViewController {
|
||||
}
|
||||
|
||||
@IBAction func btn_Break(_ sender: Any) {
|
||||
isRunning = false
|
||||
_ = 0
|
||||
NotificationCenter.default.post(name: EmulationNotifications.StopEmulation, object: nil)
|
||||
btn_Step.isEnabled = true
|
||||
}
|
||||
|
||||
@IBAction func btn_CPURun(_ sender: Any) {
|
||||
debugRun()
|
||||
NotificationCenter.default.post(name: EmulationNotifications.StartEmulation, object: nil)
|
||||
btn_Step.isEnabled = false
|
||||
}
|
||||
|
||||
@IBAction func btn_CPU_Restart(_ sender: Any) {
|
||||
cpuInstance.performReset()
|
||||
cpuInstance.program_counter = 0x400
|
||||
cpuInstance.program_counter = CPU.sharedInstance.memoryInterface.readWord(offset: 0xFFFC)
|
||||
debugConsolePrint(str: "CPU restarted from \(cpuInstance.program_counter)", newline: true)
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13168.3" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13178.6" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13168.3"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13178.6"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="PreferencesWindowController" customModule="FruitMachine" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="a2_Model" destination="TU9-bs-2Ym" id="9ZX-6b-QuF"/>
|
||||
<outlet property="a2_Peripherals_Slot0" destination="vWt-St-7kp" id="4VF-4b-kcd"/>
|
||||
<outlet property="a2_Peripherals_Slot6" destination="1CB-yF-5GW" id="zcX-Ts-Jd4"/>
|
||||
<outlet property="path_ROMBasic" destination="ilU-dc-qbI" id="HXt-ut-QQs"/>
|
||||
@ -39,7 +40,7 @@
|
||||
</view>
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Paths" identifier="2" id="zXY-01-Th8">
|
||||
<view key="view" ambiguous="YES" id="tiQ-eh-pdD">
|
||||
<view key="view" id="tiQ-eh-pdD">
|
||||
<rect key="frame" x="10" y="33" width="554" height="338"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
@ -140,12 +141,12 @@
|
||||
</view>
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Apple II" identifier="" id="aVa-m9-VEl">
|
||||
<view key="view" id="mjS-Jn-KXO">
|
||||
<view key="view" ambiguous="YES" id="mjS-Jn-KXO">
|
||||
<rect key="frame" x="10" y="33" width="554" height="338"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Vav-pa-mvK">
|
||||
<rect key="frame" x="15" y="318" width="40" height="17"/>
|
||||
<rect key="frame" x="15" y="260" width="40" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Slot 0" id="WlR-dZ-W72">
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -154,7 +155,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Rlr-ba-8ra">
|
||||
<rect key="frame" x="15" y="292" width="40" height="17"/>
|
||||
<rect key="frame" x="15" y="234" width="40" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Slot 1" id="9CT-bF-2b1">
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -163,7 +164,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Lvh-92-Ob0">
|
||||
<rect key="frame" x="59" y="286" width="200" height="26"/>
|
||||
<rect key="frame" x="59" y="228" width="200" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Nothing" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="D6o-nP-s6d" id="RLd-bf-oyA">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
@ -178,7 +179,7 @@
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tQA-LM-0T1">
|
||||
<rect key="frame" x="59" y="260" width="200" height="26"/>
|
||||
<rect key="frame" x="59" y="202" width="200" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Nothing" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="RXL-80-c5U" id="M1v-yb-ebb">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
@ -193,7 +194,7 @@
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="oeY-9x-3x1">
|
||||
<rect key="frame" x="15" y="239" width="40" height="17"/>
|
||||
<rect key="frame" x="15" y="181" width="40" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Slot 3" id="qwP-Fk-pIB">
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -202,7 +203,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ron-Jc-5NS">
|
||||
<rect key="frame" x="59" y="233" width="200" height="26"/>
|
||||
<rect key="frame" x="59" y="175" width="200" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Nothing" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="khL-Nw-Yyb" id="5Uy-Yz-oHu">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
@ -217,7 +218,7 @@
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vWt-St-7kp">
|
||||
<rect key="frame" x="59" y="313" width="200" height="26"/>
|
||||
<rect key="frame" x="59" y="255" width="200" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Nothing" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="hE4-yk-CCz" id="SSf-AV-4XO">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
@ -230,8 +231,22 @@
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="TU9-bs-2Ym">
|
||||
<rect key="frame" x="59" y="311" width="200" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Apple ][ (Original)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="4DB-sZ-RHB" id="tYw-7u-gRo">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="H16-YD-aYe">
|
||||
<items>
|
||||
<menuItem title="Apple ][ (Original)" state="on" id="4DB-sZ-RHB"/>
|
||||
<menuItem title="Apple ][+" id="lk3-qa-sTr"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wql-7f-Cmb">
|
||||
<rect key="frame" x="15" y="212" width="40" height="17"/>
|
||||
<rect key="frame" x="15" y="154" width="40" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Slot 4" id="uVy-Wv-hRJ">
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -240,7 +255,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ZVS-Jk-MpZ">
|
||||
<rect key="frame" x="15" y="186" width="40" height="17"/>
|
||||
<rect key="frame" x="15" y="128" width="40" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Slot 5" id="ua9-zw-Mzd">
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -249,7 +264,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="sjp-qD-NrB">
|
||||
<rect key="frame" x="59" y="180" width="200" height="26"/>
|
||||
<rect key="frame" x="59" y="122" width="200" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Nothing" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="LS7-45-B1l" id="6hp-fp-PHh">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
@ -264,7 +279,7 @@
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="nm9-Zu-F6e">
|
||||
<rect key="frame" x="15" y="160" width="40" height="17"/>
|
||||
<rect key="frame" x="15" y="102" width="40" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Slot 6" id="SWC-La-cqL">
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -273,7 +288,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1CB-yF-5GW">
|
||||
<rect key="frame" x="59" y="154" width="200" height="26"/>
|
||||
<rect key="frame" x="59" y="96" width="200" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Disk II" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="2jc-dw-NSC" id="bnz-0t-Peo">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
@ -287,7 +302,7 @@
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="rtE-66-3Uw">
|
||||
<rect key="frame" x="15" y="133" width="40" height="17"/>
|
||||
<rect key="frame" x="15" y="75" width="40" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Slot 7" id="9oV-LI-9Pq">
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -296,7 +311,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="al0-aZ-uIH">
|
||||
<rect key="frame" x="59" y="127" width="200" height="26"/>
|
||||
<rect key="frame" x="59" y="69" width="200" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Nothing" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Nxx-1S-5RB" id="eNb-sq-3kP">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
@ -311,7 +326,7 @@
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cyH-8e-Kfo">
|
||||
<rect key="frame" x="59" y="207" width="200" height="26"/>
|
||||
<rect key="frame" x="59" y="149" width="200" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Nothing" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="F24-4g-S0w" id="UNW-s2-DPf">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
@ -326,7 +341,7 @@
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="xvm-ee-hrf">
|
||||
<rect key="frame" x="15" y="266" width="40" height="17"/>
|
||||
<rect key="frame" x="15" y="208" width="40" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Slot 2" id="IDk-eE-egh">
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -334,6 +349,15 @@
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bx6-PM-jSt">
|
||||
<rect key="frame" x="15" y="317" width="40" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model" id="wZ2-pR-lRr">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
</view>
|
||||
</tabViewItem>
|
||||
|
@ -15,6 +15,7 @@ class PreferencesWindowController: NSWindowController {
|
||||
@IBOutlet weak var path_ROMBasic: NSTextField!
|
||||
|
||||
/* Apple II ROM paths */
|
||||
@IBOutlet weak var a2_Model: NSPopUpButton!
|
||||
|
||||
/* Apple II Peripherals */
|
||||
@IBOutlet weak var a2_Peripherals_Slot0: NSPopUpButton!
|
||||
@ -29,56 +30,52 @@ class PreferencesWindowController: NSWindowController {
|
||||
name: NSWindow.willCloseNotification,
|
||||
object: nil)
|
||||
|
||||
setupPreferences()
|
||||
super.windowDidLoad()
|
||||
}
|
||||
|
||||
func setupDefaultsIfRequired() {
|
||||
// UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
|
||||
// UserDefaults.standard.synchronize()
|
||||
var model = defaults.string(forKey: "a2_Model")
|
||||
if(model == nil) { model = "Apple ][+" }
|
||||
defaults.set(model, forKey: "a2_Model")
|
||||
|
||||
var slot0 = defaults.string(forKey: "a2_Peripherals_Slot0")
|
||||
var slot6 = defaults.string(forKey: "a2_Peripherals_Slot6")
|
||||
|
||||
if(slot0 == nil) { slot0 = "Language Card (16K)" }
|
||||
if(slot6 == nil) { slot6 = "Disk II" }
|
||||
|
||||
defaults.set(slot0, forKey: "a2_Peripherals_Slot0")
|
||||
|
||||
var slot6 = defaults.string(forKey: "a2_Peripherals_Slot6")
|
||||
if(slot6 == nil) { slot6 = "Disk II" }
|
||||
defaults.set(slot6, forKey: "a2_Peripherals_Slot6")
|
||||
}
|
||||
|
||||
func setupPreferences() {
|
||||
setupA1RomPaths()
|
||||
setupA2Peripherals()
|
||||
|
||||
setPreference(dropdown: a2_Model, key: "a2_Model")
|
||||
}
|
||||
|
||||
func setupA1RomPaths() {
|
||||
let monitorPath = defaults.string(forKey: "path_ROMMonitor")
|
||||
let characterPath = defaults.string(forKey: "path_ROMCharacter")
|
||||
let basicPath = defaults.string(forKey: "path_ROMBasic")
|
||||
|
||||
if (monitorPath != nil) {
|
||||
path_ROMMonitor.stringValue = monitorPath!
|
||||
}
|
||||
|
||||
if (characterPath != nil) {
|
||||
path_ROMCharacter.stringValue = characterPath!
|
||||
}
|
||||
|
||||
if (basicPath != nil) {
|
||||
path_ROMBasic.stringValue = basicPath!
|
||||
}
|
||||
setPreference(textfield: path_ROMMonitor, key: "path_ROMMonitor")
|
||||
setPreference(textfield: path_ROMCharacter, key: "path_ROMCharacter")
|
||||
setPreference(textfield: path_ROMBasic, key: "path_ROMBasic")
|
||||
}
|
||||
|
||||
func setupA2Peripherals() {
|
||||
let slot0 = defaults.string(forKey: "a2_Peripherals_Slot0")
|
||||
if(slot0 != nil) {
|
||||
a2_Peripherals_Slot6.selectItem(withTitle: slot0!)
|
||||
}
|
||||
setPreference(dropdown: a2_Peripherals_Slot0, key: "a2_Peripherals_Slot0")
|
||||
setPreference(dropdown: a2_Peripherals_Slot6, key: "a2_Peripherals_Slot6")
|
||||
}
|
||||
|
||||
let slot6 = defaults.string(forKey: "a2_Peripherals_Slot6")
|
||||
if(slot6 != nil) {
|
||||
a2_Peripherals_Slot6.selectItem(withTitle: slot6!)
|
||||
func setPreference(dropdown: NSPopUpButton, key: String) {
|
||||
let pref = defaults.string(forKey: key)
|
||||
if(pref != nil) {
|
||||
dropdown.selectItem(withTitle: pref!)
|
||||
}
|
||||
}
|
||||
|
||||
func setPreference(textfield: NSTextField, key: String) {
|
||||
let pref = defaults.string(forKey: key)
|
||||
if(pref != nil) {
|
||||
textfield.stringValue = pref!
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,6 +88,8 @@ class PreferencesWindowController: NSWindowController {
|
||||
defaults.set(a2_Peripherals_Slot6.selectedItem?.title, forKey: "a2_Peripherals_Slot6")
|
||||
|
||||
defaults.synchronize()
|
||||
|
||||
NotificationCenter.default.post(name: EmulationNotifications.StartEmulation, object: nil)
|
||||
}
|
||||
|
||||
override var windowNibName : NSNib.Name? {
|
||||
@ -115,7 +114,7 @@ class PreferencesWindowController: NSWindowController {
|
||||
@IBAction func btn_click_Character(_ sender: NSButton) {
|
||||
let picker = NSOpenPanel()
|
||||
|
||||
picker.title = "Select your Monitor ROM (apple1.vid)"
|
||||
picker.title = "Select your Character ROM (apple1.vid)"
|
||||
picker.showsHiddenFiles = false
|
||||
picker.canChooseFiles = true
|
||||
picker.canChooseDirectories = false
|
||||
@ -130,7 +129,7 @@ class PreferencesWindowController: NSWindowController {
|
||||
@IBAction func btn_click_BASIC(_ sender: NSButton) {
|
||||
let picker = NSOpenPanel()
|
||||
|
||||
picker.title = "Select your Monitor ROM (basic.bin)"
|
||||
picker.title = "Select your BASIC ROM (basic.bin)"
|
||||
picker.showsHiddenFiles = false
|
||||
picker.canChooseFiles = true
|
||||
picker.canChooseDirectories = false
|
||||
|
Loading…
Reference in New Issue
Block a user