diff --git a/Ample.xcodeproj/project.pbxproj b/Ample.xcodeproj/project.pbxproj index 1371d7a..5839db8 100644 --- a/Ample.xcodeproj/project.pbxproj +++ b/Ample.xcodeproj/project.pbxproj @@ -177,6 +177,8 @@ B6E4B5F024FDE2670094A35C /* apple2jp.plist in Resources */ = {isa = PBXBuildFile; fileRef = B61099F224F5F36F005CB652 /* apple2jp.plist */; }; B6E4B5F124FDE2670094A35C /* apple2.plist in Resources */ = {isa = PBXBuildFile; fileRef = B6109A0324F5F371005CB652 /* apple2.plist */; }; B6E4B5F224FDE2670094A35C /* prav8m.plist in Resources */ = {isa = PBXBuildFile; fileRef = B6109A0024F5F371005CB652 /* prav8m.plist */; }; + B6E9A18025088B1B005E7525 /* NewSlotViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B6E9A17F25088B1B005E7525 /* NewSlotViewController.m */; }; + B6E9A18325088B36005E7525 /* NewSlotView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B6E9A18125088B36005E7525 /* NewSlotView.xib */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -320,6 +322,9 @@ B6DDECCA2505A86E0093587A /* eject-hover-16x16@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "eject-hover-16x16@3x.png"; sourceTree = ""; }; B6DDECCB2505A86E0093587A /* eject-16x16.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "eject-16x16.png"; sourceTree = ""; }; B6E4B5FA24FDE2670094A35C /* Ample Lite.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Ample Lite.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + B6E9A17E25088B1B005E7525 /* NewSlotViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NewSlotViewController.h; sourceTree = ""; }; + B6E9A17F25088B1B005E7525 /* NewSlotViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NewSlotViewController.m; sourceTree = ""; }; + B6E9A18225088B36005E7525 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/NewSlotView.xib; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -449,6 +454,8 @@ B6BA257D24E99BE9005FB8FF /* Ample */ = { isa = PBXGroup; children = ( + B6E9A17E25088B1B005E7525 /* NewSlotViewController.h */, + B6E9A17F25088B1B005E7525 /* NewSlotViewController.m */, B63C1B9125008A2700511A71 /* DownloadWindowController.h */, B63C1B9225008A2700511A71 /* DownloadWindowController.m */, B6BA257E24E99BE9005FB8FF /* AppDelegate.h */, @@ -488,6 +495,7 @@ B6D6DE4224FAEE8900661A5F /* Nibs */ = { isa = PBXGroup; children = ( + B6E9A18125088B36005E7525 /* NewSlotView.xib */, B63C1B9325008A2700511A71 /* DownloadWindow.xib */, B66236BF24FDB7A6006CABD7 /* Credits.rtf */, B6D6DE3C24FADF8B00661A5F /* LaunchWindow.xib */, @@ -657,6 +665,7 @@ B6109A2A24F5F377005CB652 /* apple3.plist in Resources */, B6109A2124F5F377005CB652 /* apple2ee.plist in Resources */, B63C1BA5250192D800511A71 /* cecm.plist in Resources */, + B6E9A18325088B36005E7525 /* NewSlotView.xib in Resources */, B6DDECC025057A550093587A /* drag-handle-4x10.png in Resources */, B6DDECD22505A86E0093587A /* eject-hover-16x16@2x.png in Resources */, B6109A3624F5F377005CB652 /* dodo.plist in Resources */, @@ -754,6 +763,7 @@ buildActionMask = 2147483647; files = ( B608E17F2502FE0C00D53465 /* TransparentScroller.m in Sources */, + B6E9A18025088B1B005E7525 /* NewSlotViewController.m in Sources */, B6BA258824E99BEB005FB8FF /* main.m in Sources */, B63C1B8B24FF4BF700511A71 /* Ample.m in Sources */, B6B9EA662506A5550080E70D /* EjectButton.m in Sources */, @@ -840,6 +850,14 @@ name = LaunchWindow.xib; sourceTree = ""; }; + B6E9A18125088B36005E7525 /* NewSlotView.xib */ = { + isa = PBXVariantGroup; + children = ( + B6E9A18225088B36005E7525 /* Base */, + ); + name = NewSlotView.xib; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ diff --git a/Ample/Base.lproj/NewSlotView.xib b/Ample/Base.lproj/NewSlotView.xib new file mode 100644 index 0000000..c53c1cf --- /dev/null +++ b/Ample/Base.lproj/NewSlotView.xib @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Ample/NewSlotViewController.h b/Ample/NewSlotViewController.h new file mode 100644 index 0000000..5c85cbb --- /dev/null +++ b/Ample/NewSlotViewController.h @@ -0,0 +1,47 @@ +// +// NewSlotViewController.h +// Ample +// +// Created by Kelvin Sherlock on 9/9/2020. +// Copyright © 2020 Kelvin Sherlock. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NewSlotViewController : NSViewController + +@property NSArray *args; +@property NSDictionary *media; +@property NSSize resolution; +@property (nonatomic) NSString *machine; +@end + +@interface NewSlotViewController (OutlineView) + +@end + +@interface SlotTableCellView : NSTableCellView + +@property (weak) IBOutlet NSPopUpButton *menuButton; + +@end + + +@interface SlotItem : NSObject +@property unsigned index; +@property NSArray *children; +@property NSArray *menuItems; +@property NSInteger defaultIndex; +@property NSInteger selectedIndex; + +-(NSDictionary *)selectedItem; +-(NSDictionary *)selectedMedia; +-(BOOL)hasDefault; + +-(void)reset; +@end + + +NS_ASSUME_NONNULL_END diff --git a/Ample/NewSlotViewController.m b/Ample/NewSlotViewController.m new file mode 100644 index 0000000..c0856a2 --- /dev/null +++ b/Ample/NewSlotViewController.m @@ -0,0 +1,504 @@ +// +// NewSlotViewController.m +// Ample +// +// Created by Kelvin Sherlock on 9/9/2020. +// Copyright © 2020 Kelvin Sherlock. All rights reserved. +// + +#import "NewSlotViewController.h" + +static NSFont *ItalicMenuFont(void) { + NSFont *font = [NSFont menuFontOfSize: 0]; + NSFontDescriptor *fd = [font fontDescriptor]; + NSFontDescriptor *fd2 = [fd fontDescriptorWithSymbolicTraits: NSFontDescriptorTraitItalic]; + return [NSFont fontWithDescriptor: fd2 size: [font pointSize]]; +} + +static NSAttributedString *ItalicMenuString(NSString *s) { + static NSDictionary *attr = nil; + if (!attr) { + attr = @{ + NSFontAttributeName: ItalicMenuFont() + }; + } + return [[NSAttributedString alloc] initWithString: s attributes: attr]; +} + +@implementation SlotItem + +-(id)init { + _defaultIndex = -1; + _selectedIndex = -1; + + return self; +} + +-(NSString *)label { + static NSString *Names[] = { + @"RAM:", + @"Slot 0:", + @"Slot 1:", + @"Slot 2:", + @"Slot 3:", + @"Slot 4:", + @"Slot 5:", + @"Slot 6:", + @"Slot 7:", + @"Expansion:", + @"Auxiliary:", + @"RS232:", + @"Game I/O:", + @"Modem:", + @"Printer:" + }; + return Names[_index]; +} + +-(NSString *)flag { + + static NSString *Names[] = { + @"-ramsize", + @"-sl0", + @"-sl1", + @"-sl2", + @"-sl2", + @"-sl4", + @"-sl5", + @"-sl6", + @"-sl7", + @"-exp", + @"-aux", + @"-rs232", + @"-gameio", + @"-modem", + @"-printer" + }; + return Names[_index]; + +} + +-(void)buildMenuWithSelectedValue: (NSString *)value { + + NSMutableArray *tmp = [NSMutableArray arrayWithCapacity: [_children count]]; + _defaultIndex = -1; + _selectedIndex = -1; + + int ix = 0; + for (NSDictionary *d in _children) { + NSString *title = [d objectForKey: @"description"]; + NSMenuItem *mi = [[NSMenuItem alloc] initWithTitle: title action: NULL keyEquivalent: @""]; + + // row 0 for slots is -- None -- which should be nil... + [mi setRepresentedObject: d]; + + + BOOL disabled = [(NSNumber *)[d objectForKey: @"disabled"] boolValue]; + if (disabled) { + [mi setEnabled: NO]; + } + + BOOL def = [(NSNumber *)[d objectForKey: @"default"] boolValue]; + if (def) { + [mi setAttributedTitle: ItalicMenuString(title)]; + _defaultIndex = ix; + } + + if (value) { + NSString *v = [d objectForKey: @"value"]; + if ([value compare: v] == NSOrderedSame) { + _selectedIndex = ix; + } + } + + [tmp addObject: mi]; + ++ix; + } + + + [self setMenuItems: tmp]; + if (_selectedIndex < 0) _selectedIndex = _defaultIndex; + if (_selectedIndex < 0) _selectedIndex = 0; +} + +-(void)reset { + [self setSelectedIndex: _defaultIndex >= 0 ? _defaultIndex : 0]; +} + +-(NSDictionary *)selectedItem { + if (_selectedIndex < 0) return nil; + return [_children objectAtIndex: _selectedIndex]; +} + +-(NSDictionary *)selectedMedia { + if (_selectedIndex < 0) return nil; + NSDictionary *d = [_children objectAtIndex: _selectedIndex]; + return [d objectForKey: @"media"]; +} + +-(BOOL)hasDefault { + return _defaultIndex >= 0; +} + +-(void)prepareView: (SlotTableCellView *)view { + + NSPopUpButton *button = [view menuButton]; + NSTextField *text = [view textField]; + + [text setObjectValue: [self label]]; + + [button unbind: @"selectedIndex"]; + [[button menu] setItemArray: _menuItems]; + [button bind: @"selectedIndex" toObject: self withKeyPath: @"selectedIndex" options: nil]; + [button setTag: _index]; +} + +@end + + + + + +@interface NewSlotViewController () +@property (weak) IBOutlet NSOutlineView *outlineView; + +@end + +@implementation NewSlotViewController { + NSMutableArray *_root; + + unsigned _slots_explicit; + unsigned _slots_valid; + unsigned _slots_default; + + NSDictionary *_slot_object[16]; + NSDictionary *_slot_media[16]; + NSString *_slot_value[16]; // when explicitely set. + NSDictionary *_machine_media; + + NSDictionary *_machine_data; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do view setup here. +} + +-(void)resetMachine { + + [_root removeAllObjects]; + [_outlineView reloadData]; + + _slots_valid = 0; + _slots_explicit = 0; + _slots_default = 0; + _machine_media = nil; + _machine_data = nil; + + for (unsigned i = 0; i < 16; ++i) { + _slot_media[i] = nil; + _slot_object[i] = nil; + _slot_value[i] = nil; + } + + [self setResolution: NSMakeSize(0, 0)]; + [self setArgs: @[]]; + [self setMedia: @{}]; +} + +-(void)loadMachine { + + static NSString *Keys[] = { + @"ram", + @"sl0", @"sl1", @"sl2", @"sl3", + @"sl4", @"sl5", @"sl6", @"sl7", + @"exp", @"aux", @"rs232", + @"gameio", @"printer", @"modem", + }; + static unsigned SizeofKeys = sizeof(Keys)/sizeof(Keys[0]); + + + NSBundle *bundle = [NSBundle mainBundle]; + NSURL *url= [bundle URLForResource: _machine withExtension: @"plist"]; + + NSDictionary *d = [NSDictionary dictionaryWithContentsOfURL: url]; + + if (!d) { + [self resetMachine]; + return; + } + + NSArray *r = [d objectForKey: @"resolution"]; + NSSize res = NSMakeSize(0, 0); + if (r) { + res.width = [(NSNumber *)[r objectAtIndex: 0 /*@"width"*/] doubleValue]; + res.height = [(NSNumber *)[r objectAtIndex: 1 /*@"height"*/] doubleValue]; + } + [self setResolution: res]; + + _slots_valid = 0; + //_slots_explicit = 0; + _slots_default = 0; + + _machine_media = [d objectForKey: @"media"]; + + _machine_data = d; + + [_root removeAllObjects]; + + unsigned mask = 1; + for (unsigned i = 0; i < SizeofKeys; ++i, mask <<= 1) { + + NSString *v = [_slot_object[i] objectForKey: @"value"]; + _slot_media[i] = nil; + _slot_object[i] = nil; + if (v) _slot_value[i] = v; + + NSArray *options = [d objectForKey: Keys[i]]; + if (!options) continue; + + _slots_valid |= mask; + + SlotItem *item = [SlotItem new]; + [item setIndex: i]; + [item setChildren: options]; + [item buildMenuWithSelectedValue: _slot_value[i]]; + + if ([item defaultIndex] >= 0) { + _slots_default |= mask; + } + // default media... + _slot_media[i] = [item selectedMedia]; + [_root addObject: item]; + } + + + + [_outlineView reloadData]; + [self rebuildMedia]; + [self rebuildArgs]; +} + +-(void)setMachine: (NSString *)machine { + if (_machine == machine) return; + if (_machine && machine && [machine compare: _machine] == NSOrderedSame) return; + _machine = machine; + + if (!machine) { + [self resetMachine]; + return; + } + [self loadMachine]; +} + + + + +-(void)rebuildMedia { + + + #define _(var, o) var += [[o objectForKey: @ # var ] unsignedIntValue] + + unsigned cass = 0; + unsigned cdrm = 0; + unsigned hard = 0; + unsigned flop_5_25 = 0; + unsigned flop_3_5 = 0; + +#if 0 + for (SlotItem *item in _root) { + NSDictionary *tmp = [item selectedMedia]; + if (tmp) { + _(cass, tmp); + _(cdrm, tmp); + _(hard, tmp); + _(flop_5_25, tmp); + _(flop_3_5, tmp); + } + } +#endif +#if 1 + unsigned mask = 1; + for (unsigned i = 0; i < 16; ++i, mask <<= 1) { + + if (_slots_valid & mask) { + NSDictionary *tmp = _slot_media[i]; + if (tmp) { + _(cass, tmp); + _(cdrm, tmp); + _(hard, tmp); + _(flop_5_25, tmp); + _(flop_3_5, tmp); + } + } + } +#endif + NSDictionary *tmp = _machine_media; + if (tmp) { + _(cass, tmp); + _(cdrm, tmp); + _(hard, tmp); + _(flop_5_25, tmp); + _(flop_3_5, tmp); + } + + [self setMedia: @{ + @"cass": @(cass), + @"cdrm": @(cdrm), + @"hard": @(hard), + @"flop_5_25": @(flop_5_25), + @"flop_3_5": @(flop_3_5), + }]; + +} + + +static NSString *SlotFlagForIndex(unsigned index){ + static NSString *Names[] = { + @"-ramsize", + @"-sl0", + @"-sl1", + @"-sl2", + @"-sl2", + @"-sl4", + @"-sl5", + @"-sl6", + @"-sl7", + @"-exp", + @"-aux", + @"-rs232", + @"-gameio", + @"-modem", + @"-printer" + }; + return Names[index]; +} + +-(void)rebuildArgs { + + NSMutableArray *args = [NSMutableArray new]; + + for (SlotItem *item in _root) { + NSDictionary *d = [item selectedItem]; + if ([(NSNumber *)[d objectForKey: @"default"] boolValue]) { + continue; // default, don't include it. + } + NSString *value = [d objectForKey: @"value"]; + + if (!value || ![value length]) { + if (![item hasDefault]) continue; + value = @""; + } + [args addObject: [item flag]]; + [args addObject: value]; + } + +#if 0 + unsigned mask = 1; + for (unsigned i = 0 ; i < 16; ++i, mask <<= 1) { + + if (!(_slots_valid & mask)) continue; + NSDictionary *d = _slot_object[i]; + + if ([(NSNumber *)[d objectForKey: @"default"] boolValue]) { + continue; // default, don't include it. + } + NSString *value = [d objectForKey: @"value"]; + + if (!value) { + // if slot has a default, need to overwrite it. + if (!(_slots_default & mask)) continue; + value = @""; + } + + [args addObject: SlotFlagForIndex(i)]; + [args addObject: value]; + } +#endif + [self setArgs: args]; +} + +- (IBAction)menuChanged:(NSPopUpButton *)sender { + + unsigned index = (unsigned)[sender tag]; + unsigned mask = 1 << index; + + + // index 0 = ram = special case... + + NSDictionary *d = [[sender selectedItem] representedObject]; + + _slots_explicit |= mask; + _slot_value[index] = [d objectForKey: @"value"]; + //_slots_default &= ~mask; + + //_slot_object[index] = d; + + // media... + NSDictionary *newMedia = [d objectForKey: @"media"]; + NSDictionary *oldMedia = _slot_media[index]; + + if (newMedia != oldMedia) { + _slot_media[index] = newMedia; + [self rebuildMedia]; + } + + [self rebuildArgs]; +} +-(IBAction)resetSlots:(id)sender { + + _slots_explicit = 0; + for (unsigned i = 0; i < 16; ++i) { + _slot_media[i] = nil; + _slot_object[i] = nil; + _slot_value[i] = nil; + } + for (SlotItem *item in _root) { + [item reset]; + // if children, reset them too... + unsigned index = [item index]; + _slot_media[index] = [item selectedMedia]; + } + //[_outlineView reloadData]; // will need to reload if changing the default makes children disappear. + + [self rebuildMedia]; + [self rebuildArgs]; +} + +@end + + +@implementation NewSlotViewController (OutlineView) + + +- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item { + + if (!item) return [_root count]; + + return 0; +} + +- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item { + if (!item) return [_root objectAtIndex: index]; + return nil; +} + + +- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item { + return NO; +} + + + +- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(SlotItem *)item { + + SlotTableCellView *v = [outlineView makeViewWithIdentifier: @"MenuCell" owner: self]; + + [item prepareView: v]; + + return v; +} + + + +@end