// // Slot.m // Ample // // Created by Kelvin Sherlock on 3/6/2021. // Copyright © 2021 Kelvin Sherlock. All rights reserved. // #import #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 *_options; //NSArray *_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 *_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; }