ample/Ample/Slot.m

618 lines
15 KiB
Objective-C

//
// Slot.m
// Ample
//
// Created by Kelvin Sherlock on 3/6/2021.
// Copyright © 2021 Kelvin Sherlock. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import "Slot.h"
static NSArray *MapArray(NSArray *src, id(^fn)(id)) {
NSMutableArray *rv = [NSMutableArray arrayWithCapacity: [src count]];
for (id x in src) {
[rv addObject: fn(x)];
}
return rv;
}
static NSArray *DeepCopyArray(NSArray *src) {
if (!src) return nil;
return [[NSArray alloc] initWithArray: src copyItems: YES];
}
@interface Slot () {
NSArray<SlotOption *> *_options;
//NSArray<NSMenuItem *> *_menuItems;
}
-(SlotOption *)selectedItem;
-(void)setKeyPath;
-(void)setKeyPath: (NSString *)path;
//-(NSArray *)buildArgs: (NSMutableArray *)args prefix: (NSString *)prefix;
//-(void)buildMedia: (MediaBuilder *)builder;
//-(NSArray *)buildSerial: (NSMutableArray *)array;
-(instancetype)initWithDictionary: (NSDictionary *)dictionary devices: (NSDictionary *)devices;
@end
@interface SlotOption() {
//NSArray<Slot *> *_children;
//NSDictionary *_media;
Media _media;
NSString *_keyPath;
//NSString *_devName;
BOOL _default;
}
@property (readonly) NSArray *children;
-(instancetype)initWithDictionary: (NSDictionary *)dictionary devices: (NSDictionary *)devices;
-(NSMenuItem *)menuItem;
-(void)reset;
-(void)setKeyPath: (NSString *)path;
-(void)buildArgs: (NSMutableArray *)args;
-(void)buildMedia: (Media *)media;
-(void)buildSerial: (NSMutableDictionary *)array;
-(void)reserialize: (NSDictionary *)dict;
//-(BOOL)loadDeviceSlots: (NSDictionary *)devices;
@end
@implementation Slot
static NSDictionary *IndexMap = nil;
+(void)load {
IndexMap = @{
@"ramsize": @0,
@"sl0": @1,
@"sl1": @2,
@"sl2": @3,
@"sl3": @4,
@"sl4": @5,
@"sl5": @6,
@"sl6": @7,
@"sl7": @8,
@"exp": @9,
@"aux": @10,
@"rs232": @11,
@"gameio": @12,
@"modem": @13,
@"printer": @14,
//nubus mac
@"nb9": @15,
@"nba": @16,
@"nbb": @17,
@"nbc": @18,
@"nbd": @19,
@"nbe": @20,
@"smartport": @21,
#define kSMARTPORT 21
};
}
-(void)reset {
[self setSelectedIndex: _defaultIndex >= 0 ? _defaultIndex : 0];
for (SlotOption *s in _options) {
[s reset];
}
}
-(void)selectValue: (NSString *)value {
if (value) {
NSInteger index = 0;
for (SlotOption *item in _options) {
if ([[item value] isEqualToString: value]) {
[self setSelectedIndex: index];
return;
}
++index;
}
}
//[self setSelectedIndex: _defaultIndex >= 0 ? _defaultIndex : 0];
}
-(SlotOption *)selectedItem {
if (_selectedIndex < 0) return nil;
return [_options objectAtIndex: _selectedIndex];
}
-(NSArray *)args {
if (_selectedIndex < 0) return nil;
NSMutableArray *rv = [NSMutableArray new];
SlotOption *option = [_options objectAtIndex: _selectedIndex];
[option buildArgs: rv];
return rv;
}
-(NSDictionary *)serialize {
if (_selectedIndex < 0) return nil;
NSMutableDictionary *d = [NSMutableDictionary new];
SlotOption *option = [_options objectAtIndex: _selectedIndex];
[option buildSerial: d];
//if (![d count]) return nil; //?
return d;
}
-(void)reserialize: (NSDictionary *)dict {
// { 'sl3' : 'uthernet' }
// special case for smartport since the name isn't used.
if (_index == kSMARTPORT) {
SlotOption *option = [_options objectAtIndex: _selectedIndex];
[option reserialize: dict];
return;
}
NSString *value = [dict objectForKey: _name];
if (!value) {
//[self reset];
return;
}
// find it...
BOOL found = NO;
unsigned ix = 0;
for (SlotOption *option in _options) {
if ([value isEqualToString: [option value]]) {
[self setSelectedIndex: ix];
[option reserialize: dict];
found = YES;
break;
}
++ix;
}
}
-(Media)selectedMedia {
if (_selectedIndex < 0) return EmptyMedia;
Media media = { 0 };
SlotOption *option = [_options objectAtIndex: _selectedIndex];
[option buildMedia: &media];
return media;
}
-(NSArray *)selectedChildren {
if (_selectedIndex < 0) return nil;
return [[_options objectAtIndex: _selectedIndex] children];
}
-(id)copyWithZone:(NSZone *)zone {
Slot *child = [Slot new];
child->_index = _index;
child->_defaultIndex = _defaultIndex;
child->_selectedIndex = _selectedIndex;
child->_name = [_name copyWithZone: zone];
child->_title = [_title copyWithZone: zone];
child->_options = DeepCopyArray(_options);
#if 0
// menu could still be in use by an off-screen pop up button, so it can't be cached.
child->_menuItems = DeepCopyArray(_menuItems);
// update represented object.
NSInteger index = 0;
for (NSMenuItem *item in child->_menuItems) {
[item setRepresentedObject: child->_options[index]];
++index;
}
#endif
return child;
}
-(void)setKeyPath {
if (![_name length]) return;
for (SlotOption *o in _options)
[o setKeyPath: _name];
}
-(void)setKeyPath: (NSString *)path {
// extra logic for -fdc:0, -0, -sl6:0, etc, built-in slots.
unichar c = [_name characterAtIndex: 0];
NSString *p = nil;
if (c == ':') p = [path stringByAppendingString: _name];
else if (c == '-') p = _name;
else p = [@"-" stringByAppendingString: _name];
for (SlotOption *o in _options) {
[o setKeyPath: p];
}
}
-(instancetype)initWithDictionary: (NSDictionary *)data devices: (NSDictionary *)devices {
BOOL topLevel = NO;
_selectedIndex = -1;
_defaultIndex = -1;
_index = -1;
_name = [data objectForKey: @"name"];
_title = [data objectForKey: @"description"];
NSNumber *x = [IndexMap objectForKey: _name];
if (x) {
topLevel = YES;
_index = [x integerValue];
_name = [@"-" stringByAppendingString: _name];
_title = [_title stringByAppendingString: @":"];
}
NSArray *op = [data objectForKey: @"options"];
NSMutableArray *options = [NSMutableArray arrayWithCapacity: [op count]];
NSInteger index = 0;
for (NSDictionary *d in op) {
SlotOption *o = [[SlotOption alloc] initWithDictionary: d devices: devices];
if ([o isDefault]) {
_defaultIndex = index;
}
++index;
if (topLevel) {
[o setKeyPath: _name];
NSArray *tmp = [o children];
for (Slot *x in tmp) {
[x setIndex: _index | 0x10000];
}
}
[options addObject: o];
}
_options = options;
_selectedIndex = _defaultIndex;
if (_selectedIndex < 0) _selectedIndex = 0;
//if (topLevel) [self setKeyPath];
return self;
}
#if 0
-(instancetype)initWithName: (NSString *)name title: (NSString *)title data: (NSArray *)data {
_name = [name copy];
_title = [title copy];
_selectedIndex = -1;
_defaultIndex = -1;
_index = -1;
NSMutableArray *options = [NSMutableArray arrayWithCapacity: [data count]];
//NSMutableArray *menuItems = [NSMutableArray arrayWithCapacity: [data count]];
NSInteger index = 0;
for (NSDictionary *d in data) {
SlotOption *o = [[SlotOption alloc] initWithDictionary: d];
if ([o isDefault]) {
_defaultIndex = index;
}
++index;
[options addObject: o];
}
_options = options;
//_menuItems = menuItems;
_selectedIndex = _defaultIndex;
if (_selectedIndex < 0) _selectedIndex = 0;
return self;
}
#endif
-(NSArray *)menuItems {
//if (_menuItems) return _menuItems;
NSMutableArray *menuItems = [NSMutableArray arrayWithCapacity: [_options count]];
for (SlotOption *o in _options) {
[menuItems addObject: [o menuItem]];
}
//_menuItems = tmp;
return menuItems;
}
#if 0
-(void)loadDeviceSlots: (NSDictionary *)devices {
for (SlotOption *s in _options) {
[s loadDeviceSlots: devices];
}
}
#endif
-(void)prepareView: (SlotTableCellView *)view {
// can't cache the menu items since they
// may still be in use.
NSButton *hb = [view hamburgerButton];
NSPopUpButton *button = [view menuButton];
NSTextField *text = [view textField];
[view setObjectValue: self];
[text setObjectValue: _title];
[button unbind: @"selectedIndex"];
NSMenu *menu = [button menu];
NSArray *menuItems = [self menuItems];
// [menu setItemArray: ] doesn't work prior to 10.14, apparently.
[menu removeAllItems];
if (_index == kSMARTPORT) {
//[menu setItemArray: @[]];
[button setHidden: YES];
} else {
//[menu setItemArray: menuItems];
for (NSMenuItem *x in menuItems) [menu addItem: x];
[button bind: @"selectedIndex" toObject: self withKeyPath: @"selectedIndex" options: nil];
[button setHidden: NO];
}
[button setTag: _index];
[hb setTag: _index];
// hb visible status bound in xib.
}
/*
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueObserving/Articles/KVODependentKeys.html
*/
+ (NSSet *)keyPathsForValuesAffectingSelectedItem {
return [NSSet setWithObject: @"selectedIndex"];
}
@end
@implementation SlotOption
@synthesize isDefault = _default;
#if 0
-(instancetype)initWithDictionary: (NSDictionary *)dictionary {
_default = [(NSNumber *)[dictionary objectForKey: @"default"] boolValue];
_disabled = [(NSNumber *)[dictionary objectForKey: @"disabled"] boolValue];
_value = [dictionary objectForKey: @"value"];
//_devName = [dictionary objectForKey: @"devName"];
_title = [dictionary objectForKey: @"description"];
_media = MediaFromDictionary([dictionary objectForKey: @"media"]);
//_media = [dictionary objectForKey: @"media"];
return self;
}
#endif
-(instancetype)initWithDictionary: (NSDictionary *)data devices: (NSDictionary *)devices {
_default = [(NSNumber *)[data objectForKey: @"default"] boolValue];
_disabled = [(NSNumber *)[data objectForKey: @"disabled"] boolValue];
_value = [data objectForKey: @"value"];
_title = [data objectForKey: @"description"];
_media = MediaFromDictionary([data objectForKey: @"media"]);
NSString *devName = [data objectForKey: @"devname"];
if (devName && devices) {
NSArray *tmp = [devices objectForKey: devName];
if (tmp) _children = DeepCopyArray(tmp);
}
return self;
}
-(void)reset {
for (Slot *s in _children) {
[s reset];
}
}
-(NSMenuItem *)menuItem {
NSMenuItem *item;
extern NSAttributedString *ItalicMenuString(NSString *);
item = [[NSMenuItem alloc] initWithTitle: _title action: NULL keyEquivalent: @""];
if (_disabled) {
[item setEnabled: NO];
}
if (_default) {
[item setAttributedTitle: ItalicMenuString(_title)];
}
[item setRepresentedObject: self];
return item;
}
-(id)copyWithZone:(NSZone *)zone {
SlotOption *child = [SlotOption new];
child->_default = _default;
child->_disabled = _disabled;
child->_media = _media;
child->_value = [_value copyWithZone: zone];
//child->_devName = [_devName copyWithZone: zone];
child->_title = [_title copyWithZone: zone];
//child->_media = [_media copyWithZone: zone];
child->_keyPath = [_keyPath copyWithZone: zone];
child->_children = DeepCopyArray(_children);
return child;
}
-(void)buildArgs: (NSMutableArray *)args {
if (!_default) {
[args addObject: _keyPath];
[args addObject: _value];
}
for (Slot *s in _children) {
[[s selectedItem] buildArgs: args];
}
}
-(void)buildMedia: (Media *)media {
#undef _
#define _(name) media->name += _media.name
_(cass);
_(cdrom);
_(hard);
_(floppy_3_5);
_(floppy_5_25);
_(pseudo_disk);
#undef _
for (Slot *s in _children) {
[[s selectedItem] buildMedia: media];
}
}
-(void)reserialize: (NSDictionary *)dict {
#if 0
NSString *value = [dict objectForKey: _keyPath];
if (value) {
// don't need to do anything since set by slot.
}
#endif
for (Slot *s in _children) {
[s reserialize: dict];
}
}
-(void)buildSerial: (NSMutableDictionary *)dict {
if (!_default)
[dict setObject: _value forKey: _keyPath];
for (Slot *s in _children)
[[s selectedItem] buildSerial: dict];
}
// propogate
-(void)setKeyPath: (NSString *)path {
_keyPath = path;
if (!_children) return;
NSString *p = path;
if ([_value length]) p = [path stringByAppendingFormat: @":%@", _value];
for (Slot *s in _children) {
[s setKeyPath: p];
}
}
#if 0
-(BOOL)loadDeviceSlots: (NSDictionary *)devices {
NSArray *o = [devices objectForKey: _devName];
if (!o) return NO;
_children = DeepCopyArray(o);
return YES;
}
#endif
@end
@implementation SlotTableCellView
@end
extern NSString *InternString(NSString *);
NSDictionary *BuildDevices(NSArray *array) {
#if 0
static NSCache *cache = nil;
if (!cache) {
cache = [NSCache new];
}
#endif
NSMutableDictionary *rv = [NSMutableDictionary dictionaryWithCapacity: [array count]];
for (NSDictionary *d in array) {
NSString *name = [d objectForKey: @"name"];
NSArray *slots = [d objectForKey: @"slots"];
if (!name) continue;
if (!slots) continue;
#if 0
name = InternString(name);
id x = [cache objectForKey: name];
if (x) {
[rv setObject: x forKey: name];
continue;
}
#endif
NSArray *data = MapArray(slots, ^(id o){
Slot *s = [[Slot alloc] initWithDictionary: o devices: nil];
return s;
});
[rv setObject: data forKey: name];
}
return rv;
}
NSArray *BuildSlots(NSString *name, NSDictionary *data) {
static NSCache *cache = nil;
if (!cache) {
cache = [NSCache new];
}
name = InternString(name);
NSArray *x = [cache objectForKey: name];
if (x) {
return x;
}
NSArray *slots = [data objectForKey: @"slots"];
NSMutableArray *rv = [NSMutableArray arrayWithCapacity: [slots count]];
NSDictionary *devices = BuildDevices([data objectForKey: @"devices"]);
for (NSDictionary *d in slots) {
Slot *s = [[Slot alloc] initWithDictionary: d devices: devices];
[rv addObject: s];
}
[cache setObject: rv forKey: name];
return rv;
}