move main window to a separate nib/controller.

This commit is contained in:
Kelvin Sherlock 2020-08-29 15:42:06 -04:00
parent fe94158187
commit ef959a623b
7 changed files with 522 additions and 433 deletions

View File

@ -65,6 +65,8 @@
B6BA258824E99BEB005FB8FF /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = B6BA258724E99BEB005FB8FF /* main.m */; };
B6D6DE3924FAC8B500661A5F /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = B6D6DE3724FAC8B500661A5F /* Preferences.xib */; };
B6D6DE3B24FACF4F00661A5F /* Defaults.plist in Resources */ = {isa = PBXBuildFile; fileRef = B6D6DE3A24FACF4F00661A5F /* Defaults.plist */; };
B6D6DE3E24FADF8B00661A5F /* LaunchWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = B6D6DE3C24FADF8B00661A5F /* LaunchWindow.xib */; };
B6D6DE4124FADFAC00661A5F /* LaunchWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = B6D6DE4024FADFAC00661A5F /* LaunchWindowController.m */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@ -137,6 +139,9 @@
B6BA258924E99BEB005FB8FF /* MA2ME.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MA2ME.entitlements; sourceTree = "<group>"; };
B6D6DE3824FAC8B500661A5F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/Preferences.xib; sourceTree = "<group>"; };
B6D6DE3A24FACF4F00661A5F /* Defaults.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Defaults.plist; sourceTree = "<group>"; };
B6D6DE3D24FADF8B00661A5F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchWindow.xib; sourceTree = "<group>"; };
B6D6DE3F24FADFAC00661A5F /* LaunchWindowController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LaunchWindowController.h; sourceTree = "<group>"; };
B6D6DE4024FADFAC00661A5F /* LaunchWindowController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LaunchWindowController.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -250,9 +255,12 @@
B6BA258724E99BEB005FB8FF /* main.m */,
B6BA258924E99BEB005FB8FF /* MA2ME.entitlements */,
B6BA258324E99BEB005FB8FF /* MainMenu.xib */,
B6D6DE3C24FADF8B00661A5F /* LaunchWindow.xib */,
B6D6DE3724FAC8B500661A5F /* Preferences.xib */,
B61099E524F5F230005CB652 /* MediaView.xib */,
B61099E324F5F230005CB652 /* SlotView.xib */,
B6D6DE3F24FADFAC00661A5F /* LaunchWindowController.h */,
B6D6DE4024FADFAC00661A5F /* LaunchWindowController.m */,
);
path = MA2ME;
sourceTree = "<group>";
@ -318,6 +326,7 @@
B6109A2424F5F377005CB652 /* las128ex.plist in Resources */,
B6109A1D24F5F377005CB652 /* space84.plist in Resources */,
B6109A3F24F5F377005CB652 /* agat9.plist in Resources */,
B6D6DE3E24FADF8B00661A5F /* LaunchWindow.xib in Resources */,
B6109A3124F5F377005CB652 /* apple1.plist in Resources */,
B6109A3524F5F377005CB652 /* ace100.plist in Resources */,
B6109A3424F5F377005CB652 /* am64.plist in Resources */,
@ -377,6 +386,7 @@
files = (
B6BA258824E99BEB005FB8FF /* main.m in Sources */,
B64E15AC24EA1FD400E8AD3D /* SlotBrowserDelegate.m in Sources */,
B6D6DE4124FADFAC00661A5F /* LaunchWindowController.m in Sources */,
B64E15A924EA1D5300E8AD3D /* ROMBrowserDelegate.m in Sources */,
B64979C224EF6703008ABD20 /* MediaViewController.m in Sources */,
B60A6E1424EE0AE2004B7EEF /* FlippedView.m in Sources */,
@ -420,6 +430,14 @@
name = Preferences.xib;
sourceTree = "<group>";
};
B6D6DE3C24FADF8B00661A5F /* LaunchWindow.xib */ = {
isa = PBXVariantGroup;
children = (
B6D6DE3D24FADF8B00661A5F /* Base */,
);
name = LaunchWindow.xib;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */

View File

@ -8,7 +8,7 @@
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate, NSBrowserDelegate>
@interface AppDelegate : NSObject <NSApplicationDelegate>
- (IBAction)displayPreferences:(id)sender;

View File

@ -9,36 +9,17 @@
#import "AppDelegate.h"
#import "SlotViewController.h"
#import "MediaViewController.h"
#import "LaunchWindowController.h"
@interface AppDelegate ()
@property (weak) IBOutlet NSWindow *window;
@property (strong) IBOutlet SlotViewController *slotController;
@property (strong) IBOutlet MediaViewController *mediaController;
@property (weak) IBOutlet NSView *modelView;
@property (weak) IBOutlet NSView *slotView;
@property (weak) IBOutlet NSView *mediaView;
/* kvo */
@property NSString *commandLine;
@property NSArray *args;
@property NSString *mameROM;
@property BOOL mameWindow;
@property BOOL mameNoThrottle;
@property BOOL mameDebug;
@property BOOL mameSquarePixels;
@property NSArray *browserItems;
@end
@implementation AppDelegate {
NSWindowController *_prefs;
NSWindowController *_launcher;
}
static NSString *kMyContext = @"kMyContext";
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
@ -56,63 +37,8 @@ static NSString *kMyContext = @"kMyContext";
[[NSUserDefaults standardUserDefaults] registerDefaults: dict];
}
/* My Copy of XCode/Interface Builder barfs on NSBrowser. */
path = [bundle pathForResource: @"models" ofType: @"plist"];
_browserItems = [NSArray arrayWithContentsOfFile: path];
NSView *view = [_window contentView];
NSRect frame;
NSBrowser *browser = nil;
frame = [_modelView frame];
browser = [[NSBrowser alloc] initWithFrame: frame];
[browser setMaxVisibleColumns: 2];
//[browser setTakesTitleFromPreviousColumn: YES];
//[browser setTitled: NO];
[browser setAllowsEmptySelection: NO];
[browser setDelegate: self];
[browser setAction: @selector(modelClick:)];
[view addSubview: browser];
//[browser setTitled: YES]; // NSBrowser title bug.
#if 0
frame = [_slotView frame];
browser = [[NSBrowser alloc] initWithFrame: frame];
[browser setMaxVisibleColumns: 2];
[browser setTakesTitleFromPreviousColumn: YES];
[browser setTitled: NO];
[browser setDelegate: _slotDelegate];
//[browser setAction: @selector(modelClick:)];
[view addSubview: browser];
[browser setTitled: YES]; // NSBrowser title bug.
[_slotDelegate setBrowser: browser];
#endif
[_slotView addSubview: [_slotController view]];
[_mediaView addSubview: [_mediaController view]];
[self addObserver: self forKeyPath: @"mameROM" options:0 context: (__bridge void * _Nullable)(kMyContext)];
[self addObserver: self forKeyPath: @"mameWindow" options:0 context: (__bridge void * _Nullable)(kMyContext)];
[self addObserver: self forKeyPath: @"mameSquarePixels" options:0 context: (__bridge void * _Nullable)(kMyContext)];
[self addObserver: self forKeyPath: @"mameDebug" options:0 context: (__bridge void * _Nullable)(kMyContext)];
[self addObserver: self forKeyPath: @"mameNoThrottle" options:0 context: (__bridge void * _Nullable)(kMyContext)];
[_slotController addObserver: self forKeyPath: @"args" options: 0 context: (__bridge void * _Nullable)(kMyContext)];
[_mediaController addObserver: self forKeyPath: @"args" options: 0 context: (__bridge void * _Nullable)(kMyContext)];
[_mediaController bind: @"media" toObject: _slotController withKeyPath: @"media" options: 0];
[self buildCommandLine];
_launcher = [LaunchWindowController new];
[_launcher showWindow: nil];
}
@ -125,248 +51,10 @@ static NSString *kMyContext = @"kMyContext";
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if (context == (__bridge void *)kMyContext) {
[self buildCommandLine];
} else {
[super observeValueForKeyPath: keyPath ofObject: object change: change context: context];
}
}
static NSString * JoinArguments(NSArray *argv) {
static NSCharacterSet *safe = nil;
static NSCharacterSet *unsafe = nil;
if (!safe) {
NSString *str =
@"%+-./:=_"
@"0123456789"
@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
;
safe = [NSCharacterSet characterSetWithCharactersInString: str];
unsafe = [safe invertedSet];
}
NSMutableString *rv = [NSMutableString new];
//unsigned ix = 0;
[rv appendString: @"mame"];
for (NSString *s in argv) {
[rv appendString: @" "];
NSUInteger l = [s length];
if (!l) {
[rv appendString: @"''"];
continue;
}
if (!CFStringFindCharacterFromSet((CFStringRef)s, (CFCharacterSetRef)unsafe, CFRangeMake(0, l), 0, NULL)) {
[rv appendString: s];
continue;
}
unichar *buffer = malloc(sizeof(unichar) * l);
[s getCharacters: buffer range: NSMakeRange(0, l)];
[rv appendString: @"'"];
for (NSUInteger i = 0; i < l; ++i) {
unichar c = buffer[i];
switch (c) {
case '\'':
[rv appendString: @"\\'"];
break;
case '\\':
[rv appendString: @"\\\\"];
break;
case 0x7f:
[rv appendString: @"\\177"];
break;
default: {
NSString *cc;
if (c < 0x20) {
cc = [NSString stringWithFormat: @"\\%o", c];
} else {
cc = [NSString stringWithCharacters: &c length: 1];
}
[rv appendString: cc];
break;
}
}
}
[rv appendString: @"'"];
free(buffer);
}
return rv;
}
-(void)buildCommandLine {
if (!_mameROM) {
[self setCommandLine: @""];
return;
}
NSMutableArray *argv = [NSMutableArray new];
//[argv addObject: @"mame"];
[argv addObject: _mameROM];
if (_mameDebug) [argv addObject: @"-debug"];
if (_mameWindow) [argv addObject: @"-window"];
// -nounevenstretch -video soft
[argv addObject: @"-skip_gameinfo"];
if (_mameWindow && _mameSquarePixels) {
NSSize screen = [_slotController resolution];
NSString *res = [NSString stringWithFormat: @"%ux%u", (unsigned)screen.width, (unsigned)screen.height];
NSString *aspect = [NSString stringWithFormat: @"%u:%u", (unsigned)screen.width, (unsigned)screen.height];
[argv addObject: @"-nomax"];
[argv addObject: @"-nounevenstretch"];
if ([_mameROM hasPrefix: @"apple2gs"]) {
[argv addObject: @"-resolution"];
[argv addObject: res]; // @"704x462"];
[argv addObject: @"-video"];
[argv addObject: @"soft"];
[argv addObject: @"-aspect"];
[argv addObject: aspect];
} else {
[argv addObject: @"-resolution"];
[argv addObject: res]; // @"560x384"];
}
}
// -speed n
// -scale n
NSArray *tmp;
tmp = [_slotController args];
if ([tmp count]) {
[argv addObjectsFromArray: tmp];
}
tmp = [_mediaController args];
if ([tmp count]) {
[argv addObjectsFromArray: tmp];
}
if (_mameNoThrottle) [argv addObject: @"-nothrottle"];
[self setCommandLine: JoinArguments(argv)]; //[argv componentsJoinedByString:@" "]];
[self setArgs: argv];
}
-(IBAction)modelClick:(id)sender {
NSDictionary *item = [self itemForBrowser: sender];
NSString *model = [item objectForKey: @"value"];
[self setMameROM: model];
// [self buildCommandLine];
[_slotController setModel: model];
}
#pragma mark NSBrowser
-(NSDictionary *)itemForBrowser: (NSBrowser *)browser {
NSIndexPath *path = [browser selectionIndexPath];
NSArray *a = _browserItems;
NSDictionary *item = nil;
NSUInteger l = [path length];
for (NSUInteger i = 0; i < l; ++i) {
NSUInteger ix = [path indexAtPosition: i];
if (ix > [a count]) return nil;
item = [a objectAtIndex: ix];
a = [item objectForKey: @"children"];
}
return item;
}
-(NSArray *)itemsForBrowser: (NSBrowser *)browser column: (NSInteger) column {
NSArray *a = _browserItems;
for (unsigned i = 0; i < column; ++i) {
NSInteger ix = [browser selectedRowInColumn: i];
if (ix < 0) return 0;
NSDictionary *item = [a objectAtIndex: ix];
a = [item objectForKey: @"children"];
if (!a) return 0;
}
return a;
}
- (void)browser:(NSBrowser *)sender willDisplayCell:(id)cell atRow:(NSInteger)row column:(NSInteger)column {
NSArray *a = [self itemsForBrowser: sender column: column];
if (!a || row >= [a count]) return;
NSDictionary *item = [a objectAtIndex: row];
NSBrowserCell *bc = (NSBrowserCell *)cell;
[bc setStringValue: [item objectForKey: @"description"]];
[bc setLeaf: ![item objectForKey: @"children"]];
}
- (NSString *)browser:(NSBrowser *)sender titleOfColumn:(NSInteger)column {
return column == 0 ? @"Model" : @"Submodel";
}
#if 0
- (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
return nil;
}
-(id)rootItemForBrowser:(NSBrowser *)browser {
return _browserItems;
}
#endif
- (NSInteger)browser:(NSBrowser *)sender numberOfRowsInColumn:(NSInteger)column {
NSArray *a = [self itemsForBrowser: sender column: column];
return [a count];
}
#pragma mark - IBActions
- (IBAction)launchAction:(id)sender {
if (![_args count]) return;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *path = [defaults stringForKey: @"MamePath"];
if (![path length]) path = @"/usr/local/bin/mame";
NSError *error = nil;
NSURL *url = [NSURL fileURLWithPath: path];
NSTask *task = [NSTask launchedTaskWithExecutableURL: url
arguments: _args
error: &error
terminationHandler: ^(NSTask *t){
}];
if (error) NSLog(@"launchAction: %@", error);
}
- (IBAction)displayPreferences:(id)sender {
if (!_prefs) {

View File

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="15705" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15705"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="LaunchWindowController">
<connections>
<outlet property="mediaController" destination="t7c-zy-czN" id="a7d-HC-TWx"/>
<outlet property="mediaView" destination="J9O-xI-P5J" id="PmZ-VC-4SN"/>
<outlet property="modelView" destination="oVt-eD-aaj" id="nam-De-IQe"/>
<outlet property="slotController" destination="lyS-mc-3Tf" id="LXo-Ii-fDX"/>
<outlet property="slotView" destination="P9d-sS-qEb" id="T2q-ON-owm"/>
<outlet property="window" destination="Vze-YF-m6e" id="JUs-Eb-MW8"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="MA2ME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="Vze-YF-m6e">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="335" y="390" width="718" height="947"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<view key="contentView" id="Ssv-Jw-EE0">
<rect key="frame" x="0.0" y="0.0" width="718" height="947"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="oVt-eD-aaj">
<rect key="frame" x="0.0" y="747" width="718" height="200"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
</customView>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="zVI-uU-muo">
<rect key="frame" x="18" y="102" width="71" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Window" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="vx1-I7-8kN">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="-2" name="value" keyPath="self.mameWindow" id="uS7-cg-wqb"/>
</connections>
</button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ZUp-ni-Rr6">
<rect key="frame" x="18" y="82" width="62" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Debug" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="6is-QN-JEc">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="-2" name="value" keyPath="self.mameDebug" id="vkc-sR-KnB"/>
</connections>
</button>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pKK-yY-vnY">
<rect key="frame" x="0.0" y="0.0" width="718" height="52"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" selectable="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" drawsBackground="YES" id="bk2-0p-jUj">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.commandLine" id="9gW-Y2-kvp"/>
</connections>
</textField>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="nKp-px-nHg">
<rect key="frame" x="621" y="53" width="85" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<buttonCell key="cell" type="push" title="Launch" bezelStyle="rounded" image="NSAppleMenuImage" imagePosition="left" alignment="center" borderStyle="border" inset="2" id="Ct1-Rb-7uI">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="launchAction:" target="-2" id="a4d-zv-1CU"/>
</connections>
</button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="rLP-n2-0eY">
<rect key="frame" x="18" y="62" width="91" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="No Throttle" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="NyE-jT-WqC">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="-2" name="value" keyPath="self.mameNoThrottle" id="tUN-EX-7PF"/>
</connections>
</button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="D5h-dm-fuZ">
<rect key="frame" x="131" y="102" width="104" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Square Pixels" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="yWK-BP-7Z9">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="-2" name="value" keyPath="self.mameSquarePixels" id="BiP-wC-Hpn"/>
</connections>
</button>
<customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="P9d-sS-qEb" customClass="FlippedView">
<rect key="frame" x="20" y="169" width="306" height="570"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</customView>
<customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="J9O-xI-P5J" customClass="FlippedView">
<rect key="frame" x="392" y="169" width="306" height="570"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
</customView>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="zoQ-mU-ARl">
<rect key="frame" x="261" y="102" width="67" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="No Blur" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="r3s-5h-Xv5">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="-2" name="value" keyPath="self.mameNoBlur" id="Pvn-zw-OsQ"/>
</connections>
</button>
</subviews>
</view>
<point key="canvasLocation" x="769" y="196.5"/>
</window>
<viewController title="Media View" nibName="MediaView" id="t7c-zy-czN" customClass="MediaViewController"/>
<viewController title="Slot View" nibName="SlotView" id="lyS-mc-3Tf" customClass="SlotViewController"/>
</objects>
<resources>
<image name="NSAppleMenuImage" width="11" height="14"/>
</resources>
</document>

View File

@ -3,7 +3,6 @@
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15705"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
@ -13,16 +12,7 @@
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate">
<connections>
<outlet property="mediaController" destination="kiU-IX-LTW" id="HXj-YL-bBX"/>
<outlet property="mediaView" destination="Jcd-jI-Pje" id="d1b-R8-L4a"/>
<outlet property="modelView" destination="jf6-4d-ms1" id="HUy-kg-cCk"/>
<outlet property="slotController" destination="CYF-Xd-EdF" id="14d-v7-98c"/>
<outlet property="slotView" destination="cFJ-CO-Dzm" id="u9o-0O-D5i"/>
<outlet property="window" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
</connections>
</customObject>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate"/>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
@ -690,110 +680,5 @@
</items>
<point key="canvasLocation" x="132" y="154"/>
</menu>
<window title="MA2ME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="335" y="390" width="718" height="947"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<view key="contentView" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="718" height="947"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="jf6-4d-ms1">
<rect key="frame" x="0.0" y="747" width="718" height="200"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
</customView>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ymx-Ml-AsE">
<rect key="frame" x="18" y="102" width="71" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Window" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="McP-bf-5Zu">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="Voe-Tx-rLC" name="value" keyPath="self.mameWindow" id="0bg-kj-1JX"/>
</connections>
</button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bkv-BK-7TC">
<rect key="frame" x="18" y="82" width="62" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Debug" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="WPk-zw-UFD">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="Voe-Tx-rLC" name="value" keyPath="self.mameDebug" id="mGk-ML-ncm"/>
</connections>
</button>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="g2S-iX-zTe">
<rect key="frame" x="0.0" y="0.0" width="718" height="52"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" selectable="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" drawsBackground="YES" id="vbg-gA-6dg">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="Voe-Tx-rLC" name="value" keyPath="self.commandLine" id="gkK-GV-onk"/>
</connections>
</textField>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ug9-kz-ZSS">
<rect key="frame" x="621" y="53" width="85" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<buttonCell key="cell" type="push" title="Launch" bezelStyle="rounded" image="NSAppleMenuImage" imagePosition="leading" alignment="center" borderStyle="border" inset="2" id="evD-kp-H2u">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="launchAction:" target="Voe-Tx-rLC" id="469-Yy-fyx"/>
</connections>
</button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="QSr-Gw-xV3">
<rect key="frame" x="18" y="62" width="91" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="No Throttle" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="Kgs-UM-HeU">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="Voe-Tx-rLC" name="value" keyPath="self.mameNoThrottle" id="v1f-ka-XkT"/>
</connections>
</button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PUB-So-kAo">
<rect key="frame" x="131" y="102" width="104" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Square Pixels" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="Bj2-i5-4h1">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="Voe-Tx-rLC" name="value" keyPath="self.mameSquarePixels" id="rNk-1d-QZl"/>
</connections>
</button>
<customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cFJ-CO-Dzm" customClass="FlippedView">
<rect key="frame" x="20" y="169" width="306" height="570"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</customView>
<customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Jcd-jI-Pje" customClass="FlippedView">
<rect key="frame" x="392" y="169" width="306" height="570"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
</customView>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="XMQ-jr-w2i">
<rect key="frame" x="261" y="102" width="67" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="No Blur" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="Xxl-Eu-ziO">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
</subviews>
</view>
<point key="canvasLocation" x="769" y="196.5"/>
</window>
<viewController title="Slot View" nibName="SlotView" id="CYF-Xd-EdF" customClass="SlotViewController"/>
<viewController title="Media View" nibName="MediaView" id="kiU-IX-LTW" customClass="MediaViewController"/>
</objects>
<resources>
<image name="NSAppleMenuImage" width="11" height="14"/>
</resources>
</document>

View File

@ -0,0 +1,17 @@
//
// LaunchWindowController.h
// MA2ME
//
// Created by Kelvin Sherlock on 8/29/2020.
// Copyright © 2020 Kelvin Sherlock. All rights reserved.
//
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
@interface LaunchWindowController : NSWindowController <NSBrowserDelegate>
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,351 @@
//
// LaunchWindowController.m
// MA2ME
//
// Created by Kelvin Sherlock on 8/29/2020.
// Copyright © 2020 Kelvin Sherlock. All rights reserved.
//
#import "LaunchWindowController.h"
#import "MediaViewController.h"
#import "SlotViewController.h"
static NSString *kMyContext = @"kMyContext";
@interface LaunchWindowController ()
@property (strong) IBOutlet MediaViewController *mediaController;
@property (strong) IBOutlet SlotViewController *slotController;
@property (weak) IBOutlet NSView *modelView;
@property (weak) IBOutlet NSView *slotView;
@property (weak) IBOutlet NSView *mediaView;
/* kvo */
@property NSString *commandLine;
@property NSArray *args;
@property NSString *mameMachine;
@property BOOL mameWindow;
@property BOOL mameNoThrottle;
@property BOOL mameDebug;
@property BOOL mameSquarePixels;
@property BOOL mameNoBlur;
@property NSArray *browserItems;
@end
@implementation LaunchWindowController
-(NSString *)windowNibName {
return @"LaunchWindow";
}
- (void)windowDidLoad {
[super windowDidLoad];
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
NSBundle *bundle = [NSBundle mainBundle];
NSString *path;
NSWindow *window = [self window];
/* My Copy of XCode/Interface Builder barfs on NSBrowser. */
path = [bundle pathForResource: @"models" ofType: @"plist"];
_browserItems = [NSArray arrayWithContentsOfFile: path];
NSView *view = [window contentView];
NSRect frame;
NSBrowser *browser = nil;
frame = [_modelView frame];
browser = [[NSBrowser alloc] initWithFrame: frame];
[browser setMaxVisibleColumns: 2];
//[browser setTakesTitleFromPreviousColumn: YES];
//[browser setTitled: NO];
[browser setAllowsEmptySelection: NO];
[browser setDelegate: self];
[browser setAction: @selector(modelClick:)];
[view addSubview: browser];
//[browser setTitled: YES]; // NSBrowser title bug.
[_slotView addSubview: [_slotController view]];
[_mediaView addSubview: [_mediaController view]];
[self addObserver: self forKeyPath: @"mameMachine" options:0 context: (__bridge void * _Nullable)(kMyContext)];
[self addObserver: self forKeyPath: @"mameWindow" options:0 context: (__bridge void * _Nullable)(kMyContext)];
[self addObserver: self forKeyPath: @"mameSquarePixels" options:0 context: (__bridge void * _Nullable)(kMyContext)];
[self addObserver: self forKeyPath: @"mameDebug" options:0 context: (__bridge void * _Nullable)(kMyContext)];
[self addObserver: self forKeyPath: @"mameNoThrottle" options:0 context: (__bridge void * _Nullable)(kMyContext)];
[self addObserver: self forKeyPath: @"mameNoBlur" options:0 context: (__bridge void * _Nullable)(kMyContext)];
[_slotController addObserver: self forKeyPath: @"args" options: 0 context: (__bridge void * _Nullable)(kMyContext)];
[_mediaController addObserver: self forKeyPath: @"args" options: 0 context: (__bridge void * _Nullable)(kMyContext)];
[_mediaController bind: @"media" toObject: _slotController withKeyPath: @"media" options: 0];
[self buildCommandLine];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if (context == (__bridge void *)kMyContext) {
[self buildCommandLine];
} else {
[super observeValueForKeyPath: keyPath ofObject: object change: change context: context];
}
}
static NSString * JoinArguments(NSArray *argv) {
static NSCharacterSet *safe = nil;
static NSCharacterSet *unsafe = nil;
if (!safe) {
NSString *str =
@"%+-./:=_"
@"0123456789"
@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
;
safe = [NSCharacterSet characterSetWithCharactersInString: str];
unsafe = [safe invertedSet];
}
NSMutableString *rv = [NSMutableString new];
//unsigned ix = 0;
[rv appendString: @"mame"];
for (NSString *s in argv) {
[rv appendString: @" "];
NSUInteger l = [s length];
if (!l) {
[rv appendString: @"''"];
continue;
}
if (!CFStringFindCharacterFromSet((CFStringRef)s, (CFCharacterSetRef)unsafe, CFRangeMake(0, l), 0, NULL)) {
[rv appendString: s];
continue;
}
unichar *buffer = malloc(sizeof(unichar) * l);
[s getCharacters: buffer range: NSMakeRange(0, l)];
[rv appendString: @"'"];
for (NSUInteger i = 0; i < l; ++i) {
unichar c = buffer[i];
switch (c) {
case '\'':
[rv appendString: @"\\'"];
break;
case '\\':
[rv appendString: @"\\\\"];
break;
case 0x7f:
[rv appendString: @"\\177"];
break;
default: {
NSString *cc;
if (c < 0x20) {
cc = [NSString stringWithFormat: @"\\%o", c];
} else {
cc = [NSString stringWithCharacters: &c length: 1];
}
[rv appendString: cc];
break;
}
}
}
[rv appendString: @"'"];
free(buffer);
}
return rv;
}
-(void)buildCommandLine {
if (!_mameMachine) {
[self setCommandLine: @""];
return;
}
NSMutableArray *argv = [NSMutableArray new];
//[argv addObject: @"mame"];
[argv addObject: _mameMachine];
if (_mameDebug) [argv addObject: @"-debug"];
if (_mameWindow) [argv addObject: @"-window"];
// -nounevenstretch -video soft
[argv addObject: @"-skip_gameinfo"];
if (_mameWindow && _mameSquarePixels) {
NSSize screen = [_slotController resolution];
NSString *res = [NSString stringWithFormat: @"%ux%u", (unsigned)screen.width, (unsigned)screen.height];
NSString *aspect = [NSString stringWithFormat: @"%u:%u", (unsigned)screen.width, (unsigned)screen.height];
[argv addObject: @"-nomax"];
[argv addObject: @"-nounevenstretch"];
[argv addObject: @"-resolution"];
[argv addObject: res];
[argv addObject: @"-aspect"];
[argv addObject: aspect];
if (_mameNoBlur) {
[argv addObject: @"-video"];
[argv addObject: @"soft"];
}
}
// -speed n
// -scale n
NSArray *tmp;
tmp = [_slotController args];
if ([tmp count]) {
[argv addObjectsFromArray: tmp];
}
tmp = [_mediaController args];
if ([tmp count]) {
[argv addObjectsFromArray: tmp];
}
if (_mameNoThrottle) [argv addObject: @"-nothrottle"];
[self setCommandLine: JoinArguments(argv)]; //[argv componentsJoinedByString:@" "]];
[self setArgs: argv];
}
# pragma mark - IBActions
-(IBAction)modelClick:(id)sender {
NSDictionary *item = [self itemForBrowser: sender];
NSString *model = [item objectForKey: @"value"];
[self setMameMachine: model];
// [self buildCommandLine];
[_slotController setModel: model];
}
- (IBAction)launchAction:(id)sender {
if (![_args count]) return;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *path = [defaults stringForKey: @"MamePath"];
if (![path length]) path = @"/usr/local/bin/mame";
NSError *error = nil;
NSURL *url = [NSURL fileURLWithPath: path];
NSTask *task = [NSTask new];
[task setExecutableURL: url];
[task setArguments: _args];
[task setTerminationHandler: ^(NSTask *t){
}];
// set stderr/stdout...
[task launchAndReturnError: &error];
if (error) NSLog(@"launchAction: %@", error);
}
#pragma mark - NSBrowser
-(NSDictionary *)itemForBrowser: (NSBrowser *)browser {
NSIndexPath *path = [browser selectionIndexPath];
NSArray *a = _browserItems;
NSDictionary *item = nil;
NSUInteger l = [path length];
for (NSUInteger i = 0; i < l; ++i) {
NSUInteger ix = [path indexAtPosition: i];
if (ix > [a count]) return nil;
item = [a objectAtIndex: ix];
a = [item objectForKey: @"children"];
}
return item;
}
-(NSArray *)itemsForBrowser: (NSBrowser *)browser column: (NSInteger) column {
NSArray *a = _browserItems;
for (unsigned i = 0; i < column; ++i) {
NSInteger ix = [browser selectedRowInColumn: i];
if (ix < 0) return 0;
NSDictionary *item = [a objectAtIndex: ix];
a = [item objectForKey: @"children"];
if (!a) return 0;
}
return a;
}
- (void)browser:(NSBrowser *)sender willDisplayCell:(id)cell atRow:(NSInteger)row column:(NSInteger)column {
NSArray *a = [self itemsForBrowser: sender column: column];
if (!a || row >= [a count]) return;
NSDictionary *item = [a objectAtIndex: row];
NSBrowserCell *bc = (NSBrowserCell *)cell;
[bc setStringValue: [item objectForKey: @"description"]];
[bc setLeaf: ![item objectForKey: @"children"]];
}
- (NSString *)browser:(NSBrowser *)sender titleOfColumn:(NSInteger)column {
return column == 0 ? @"Model" : @"Submodel";
}
#if 0
- (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
return nil;
}
-(id)rootItemForBrowser:(NSBrowser *)browser {
return _browserItems;
}
#endif
- (NSInteger)browser:(NSBrowser *)sender numberOfRowsInColumn:(NSInteger)column {
NSArray *a = [self itemsForBrowser: sender column: column];
return [a count];
}
@end