From 3d4d0c92c7e330128e6fa984d2b974c1f19df44d Mon Sep 17 00:00:00 2001 From: Kelvin Sherlock Date: Thu, 27 May 2021 19:42:28 -0400 Subject: [PATCH] Squashed commit of the following: commit 1dd1d6d3eee928d283dafbaf4432b053dff65ac2 Author: Kelvin Sherlock Date: Thu May 27 13:30:38 2021 -0400 0.232 rom update commit cfb7f9a4d1647c41fab3328bada0f37bdb041983 Author: Kelvin Sherlock Date: Wed May 26 20:31:16 2021 -0400 fix resizing to prevent horizontal scrolling commit 5c393ab5a95c9e53c4ef1e0b3f2b77db93e1c29b Author: Kelvin Sherlock Date: Wed May 26 20:01:48 2021 -0400 esc should close the menu if visible, otherwise clear the text. commit 7da142b64280c39e216584cedb7fb14243d573f0 Author: Kelvin Sherlock Date: Tue May 25 23:17:10 2021 -0400 show software short name unless it's ambiguous. commit 6f3b3bef5e18e78619959613e28064eb6251ce27 Author: Kelvin Sherlock Date: Tue May 25 23:16:47 2021 -0400 cleanup some auto complete rough edges commit 4988d8b266b8b11e519bcb5aab05ad2c3ffe5c23 Author: Kelvin Sherlock Date: Tue May 25 23:16:24 2021 -0400 fix commit 6d4bf94fd6b4850564b705ea15e91079b00346c2 Author: Kelvin Sherlock Date: Tue May 25 19:13:28 2021 -0400 clean up autocomplete a little... commit 3722271e51d93599903bccc721dc1de61f0aae72 Author: Kelvin Sherlock Date: Tue May 25 19:13:07 2021 -0400 move auto complete logic to SoftwareSet commit 04cc83ecbe9ee4c9100305712c47a11359327734 Author: Kelvin Sherlock Date: Mon May 24 20:46:39 2021 -0400 software - keep the list name to differentiate in case of name clashes. commit 8e5b5dc7e283f760a16011308298e999110fc052 Author: Kelvin Sherlock Date: Mon May 24 20:45:13 2021 -0400 Autocomplete - limit menu content width to the clip view content width. prevents horizontal scrolling. commit 356048d450447485bb33a4261018f85dac19a134 Author: Kelvin Sherlock Date: Mon May 24 18:45:42 2021 -0400 add auto complete software list to the launch window. commit 9dc72b702c9b36d490349f291c10b98b56178567 Author: Kelvin Sherlock Date: Mon May 24 18:44:28 2021 -0400 autocomplete control. commit 40eee83f56d957c64e0f8d927486f13165b9e120 Author: Kelvin Sherlock Date: Mon May 24 18:39:32 2021 -0400 add filters to software list. commit cebfbaa545441f061cffafe8ca93db2377240a06 Author: Kelvin Sherlock Date: Sun May 23 11:59:00 2021 -0400 add compatibility field for software list filtering. --- Ample.xcodeproj/project.pbxproj | 22 + Ample/AutocompleteControl.h | 42 ++ Ample/AutocompleteControl.m | 822 ++++++++++++++++++++++++++++++ Ample/Base.lproj/Autocomplete.xib | 55 ++ Ample/Base.lproj/LaunchWindow.xib | 15 +- Ample/LaunchWindowController.m | 52 ++ Ample/Resources/ace100.plist | 7 +- Ample/Resources/ace1000.plist | 7 +- Ample/Resources/albert.plist | 7 +- Ample/Resources/am100.plist | 7 +- Ample/Resources/am64.plist | 7 +- Ample/Resources/apple2.plist | 7 +- Ample/Resources/apple2c.plist | 7 +- Ample/Resources/apple2cp.plist | 7 +- Ample/Resources/apple2e.plist | 7 +- Ample/Resources/apple2ee.plist | 7 +- Ample/Resources/apple2eefr.plist | 7 +- Ample/Resources/apple2ees.plist | 7 +- Ample/Resources/apple2eeuk.plist | 7 +- Ample/Resources/apple2ep.plist | 7 +- Ample/Resources/apple2euk.plist | 7 +- Ample/Resources/apple2gs.plist | 7 +- Ample/Resources/apple2gsr0.plist | 7 +- Ample/Resources/apple2gsr1.plist | 7 +- Ample/Resources/apple2jp.plist | 7 +- Ample/Resources/apple2p.plist | 7 +- Ample/Resources/basis108.plist | 7 +- Ample/Resources/cec2000.plist | 7 +- Ample/Resources/cece.plist | 7 +- Ample/Resources/cecg.plist | 7 +- Ample/Resources/ceci.plist | 7 +- Ample/Resources/cecm.plist | 7 +- Ample/Resources/craft2p.plist | 7 +- Ample/Resources/dodo.plist | 7 +- Ample/Resources/elppa.plist | 7 +- Ample/Resources/hkc8800a.plist | 7 +- Ample/Resources/ivelultr.plist | 7 +- Ample/Resources/las128e2.plist | 7 +- Ample/Resources/las128ex.plist | 7 +- Ample/Resources/laser128.plist | 7 +- Ample/Resources/laser2c.plist | 7 +- Ample/Resources/maxxi.plist | 7 +- Ample/Resources/microeng.plist | 7 +- Ample/Resources/mprof3.plist | 7 +- Ample/Resources/prav82.plist | 7 +- Ample/Resources/prav8c.plist | 7 +- Ample/Resources/prav8m.plist | 7 +- Ample/Resources/roms.plist | 2 + Ample/Resources/space84.plist | 7 +- Ample/Resources/spectred.plist | 7 +- Ample/Resources/tk3000.plist | 7 +- Ample/Resources/uniap2en.plist | 7 +- Ample/Resources/uniap2pt.plist | 7 +- Ample/Resources/uniap2ti.plist | 7 +- Ample/Resources/zijini.plist | 7 +- Ample/SoftwareList.h | 23 +- Ample/SoftwareList.m | 420 ++++++++++++++- python/mkmachines.py | 8 +- 58 files changed, 1734 insertions(+), 63 deletions(-) create mode 100644 Ample/AutocompleteControl.h create mode 100644 Ample/AutocompleteControl.m create mode 100644 Ample/Base.lproj/Autocomplete.xib diff --git a/Ample.xcodeproj/project.pbxproj b/Ample.xcodeproj/project.pbxproj index 80cb105..e1ab92a 100644 --- a/Ample.xcodeproj/project.pbxproj +++ b/Ample.xcodeproj/project.pbxproj @@ -133,6 +133,10 @@ B66236A924FD9A34006CABD7 /* PreferencesWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = B66236A824FD9A34006CABD7 /* PreferencesWindowController.m */; }; B66236B524FDA527006CABD7 /* SDL2.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B66236B224FDA522006CABD7 /* SDL2.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; B66236C124FDB7A6006CABD7 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = B66236BF24FDB7A6006CABD7 /* Credits.rtf */; }; + B6665C14265A0E3E00254939 /* AutocompleteControl.m in Sources */ = {isa = PBXBuildFile; fileRef = B6665C13265A0E3E00254939 /* AutocompleteControl.m */; }; + B6665C15265A0E3E00254939 /* AutocompleteControl.m in Sources */ = {isa = PBXBuildFile; fileRef = B6665C13265A0E3E00254939 /* AutocompleteControl.m */; }; + B6665C1C265C639A00254939 /* Autocomplete.xib in Resources */ = {isa = PBXBuildFile; fileRef = B6665C1A265C639900254939 /* Autocomplete.xib */; }; + B6665C1D265C639A00254939 /* Autocomplete.xib in Resources */ = {isa = PBXBuildFile; fileRef = B6665C1A265C639900254939 /* Autocomplete.xib */; }; B66D0FE72611386C000902F1 /* SoftwareList.m in Sources */ = {isa = PBXBuildFile; fileRef = B66D0FE62611386B000902F1 /* SoftwareList.m */; }; B66D0FE82611386C000902F1 /* SoftwareList.m in Sources */ = {isa = PBXBuildFile; fileRef = B66D0FE62611386B000902F1 /* SoftwareList.m */; }; B6841BD7251EC926006A5C39 /* vmnet_helper.c in Sources */ = {isa = PBXBuildFile; fileRef = B6841BCA251EC88E006A5C39 /* vmnet_helper.c */; }; @@ -423,6 +427,9 @@ B66236B224FDA522006CABD7 /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL2.framework; path = embedded/SDL2.framework; sourceTree = ""; }; B66236B824FDA698006CABD7 /* mame64 */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; name = mame64; path = embedded/mame64; sourceTree = ""; }; B66236C024FDB7A6006CABD7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = Base; path = Base.lproj/Credits.rtf; sourceTree = ""; }; + B6665C12265A0E3E00254939 /* AutocompleteControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AutocompleteControl.h; sourceTree = ""; }; + B6665C13265A0E3E00254939 /* AutocompleteControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AutocompleteControl.m; sourceTree = ""; }; + B6665C1B265C639900254939 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/Autocomplete.xib; sourceTree = ""; }; B66D0FE62611386B000902F1 /* SoftwareList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SoftwareList.m; sourceTree = ""; }; B66D0FE926113AA8000902F1 /* SoftwareList.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SoftwareList.h; sourceTree = ""; }; B67BD48424EE249D0073E334 /* apple1.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = apple1.plist; sourceTree = ""; }; @@ -662,6 +669,8 @@ B6BA257D24E99BE9005FB8FF /* Ample */ = { isa = PBXGroup; children = ( + B6665C12265A0E3E00254939 /* AutocompleteControl.h */, + B6665C13265A0E3E00254939 /* AutocompleteControl.m */, B6BA257E24E99BE9005FB8FF /* AppDelegate.h */, B6BA257F24E99BE9005FB8FF /* AppDelegate.m */, B63C1B8924FF4B7100511A71 /* Ample.h */, @@ -727,6 +736,7 @@ B6D6DE4224FAEE8900661A5F /* Nibs */ = { isa = PBXGroup; children = ( + B6665C1A265C639900254939 /* Autocomplete.xib */, B6E9A18125088B36005E7525 /* NewSlotView.xib */, B63C1B9325008A2700511A71 /* DownloadWindow.xib */, B66236BF24FDB7A6006CABD7 /* Credits.rtf */, @@ -933,6 +943,7 @@ B6109A1A24F5F377005CB652 /* microeng.plist in Resources */, B6374AD5260ECB400045CA16 /* maciisi.plist in Resources */, B6109A3E24F5F377005CB652 /* albert.plist in Resources */, + B6665C1C265C639A00254939 /* Autocomplete.xib in Resources */, B66236C124FDB7A6006CABD7 /* Credits.rtf in Resources */, B6109A4224F5F377005CB652 /* apple2p.plist in Resources */, B65085B925B616AC00354EC9 /* maclc2.plist in Resources */, @@ -1001,6 +1012,7 @@ B63C1BA6250192D800511A71 /* cecm.plist in Resources */, B6E4B5BF24FDE2670094A35C /* agat9.plist in Resources */, B6E4B5C024FDE2670094A35C /* LaunchWindow.xib in Resources */, + B6665C1D265C639A00254939 /* Autocomplete.xib in Resources */, B6DE323726044C1100418375 /* caution@2x.png in Resources */, B6E4B5C124FDE2670094A35C /* apple1.plist in Resources */, B6E4B5C224FDE2670094A35C /* ace100.plist in Resources */, @@ -1143,6 +1155,7 @@ B6152B5A25F5B57E00605E6E /* Media.m in Sources */, B6152B5625F4549F00605E6E /* Slot.m in Sources */, B6D6DE4124FADFAC00661A5F /* LaunchWindowController.m in Sources */, + B6665C14265A0E3E00254939 /* AutocompleteControl.m in Sources */, B64E15A924EA1D5300E8AD3D /* MachineViewController.m in Sources */, B64979C224EF6703008ABD20 /* MediaViewController.m in Sources */, B60A6E1424EE0AE2004B7EEF /* FlippedView.m in Sources */, @@ -1177,6 +1190,7 @@ B6E4B5B324FDE2670094A35C /* MediaViewController.m in Sources */, B64AF1F7250ED5E400A09B9B /* TableCellView.m in Sources */, B6E4B5B424FDE2670094A35C /* FlippedView.m in Sources */, + B6665C15265A0E3E00254939 /* AutocompleteControl.m in Sources */, B6E4B5B524FDE2670094A35C /* AppDelegate.m in Sources */, B66D0FE82611386C000902F1 /* SoftwareList.m in Sources */, B6E4B5B624FDE2670094A35C /* LogWindowController.m in Sources */, @@ -1228,6 +1242,14 @@ name = Credits.rtf; sourceTree = ""; }; + B6665C1A265C639900254939 /* Autocomplete.xib */ = { + isa = PBXVariantGroup; + children = ( + B6665C1B265C639900254939 /* Base */, + ); + name = Autocomplete.xib; + sourceTree = ""; + }; B6BA258324E99BEB005FB8FF /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( diff --git a/Ample/AutocompleteControl.h b/Ample/AutocompleteControl.h new file mode 100644 index 0000000..25a7622 --- /dev/null +++ b/Ample/AutocompleteControl.h @@ -0,0 +1,42 @@ +// +// AutocompleteControl.h +// Autocomplete +// +// Created by Kelvin Sherlock on 2/20/2021. +// Copyright © 2021 Kelvin Sherlock. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class AutocompleteControl; + +@protocol AutocompleteItem +-(NSString *)menuTitle; +-(NSAttributedString *)menuAttributedTitle; //?? can it still handle color? +-(BOOL)menuEnabled; +-(BOOL)menuIsHeader; +@end + + +@protocol AutoCompleteDelegate + +-(NSArray> *)autocomplete: (AutocompleteControl *)control completionsForString: (NSString *)string; +-(NSArray> *)autocomplete: (AutocompleteControl *)control completionsForItem: (id)item; + +@end + +@interface AutocompleteControl : NSSearchField + +@property NSInteger minWidth; +@property NSInteger maxDisplayItems; +@property (nullable, weak) id autocompleteDelegate; + +-(void)invalidate; + + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Ample/AutocompleteControl.m b/Ample/AutocompleteControl.m new file mode 100644 index 0000000..4e67e14 --- /dev/null +++ b/Ample/AutocompleteControl.m @@ -0,0 +1,822 @@ +// +// AutocompleteControl.m +// Autocomplete +// +// Created by Kelvin Sherlock on 2/20/2021. +// Copyright © 2021 Kelvin Sherlock. All rights reserved. +// + +#import "AutocompleteControl.h" +#include + + +/* + +Todo -- +- when there is a value, can filter the list by only including header items and the selected value +- draw inactive menu items +- when menu is hidden then text is manually deleted (not esc canceled), then down/up arrow the list needs to update. +- eliminate nib and do it manually. +- when menus is too tall, macos moves it to the top of the screen. + - 1. it's not moved someplace more appropriate when the size shrinks + - 2. it should display to the left or right in that case. + - need to know parent's frame. + + - fuzzy search - minimum distance between letters? + */ + + +@interface ACMenuView : NSView +@property (nonatomic) NSArray> *items; + +@property (weak) AutocompleteControl *parent; + +-(void)reset; +@end + + +@interface AutocompleteControl () +{ + IBOutlet NSPanel *_panel; + __weak IBOutlet ACMenuView *_menuView; + __weak IBOutlet NSScrollView *_scrollView; + + id _value; + BOOL _editing; + BOOL _dirty; +} + +@end + +@interface AutocompleteControl (SearchField) +-(void)fixTextColor: (BOOL)editing; +@end + +@implementation AutocompleteControl + +-(void)_init { + + [self setDelegate: self]; + [self setPlaceholderString: @""]; + [(NSSearchFieldCell *)[self cell] setSearchButtonCell: nil]; + + NSBundle *bundle = [NSBundle mainBundle]; + NSNib *nib = [[NSNib alloc] initWithNibNamed: @"Autocomplete" bundle: bundle]; + + NSArray *topLevel = nil; + [nib instantiateWithOwner: self topLevelObjects: &topLevel]; + + [_panel setMovable: NO]; + [_panel setBecomesKeyOnlyIfNeeded: YES]; + [_menuView setParent: self]; +} + +-(id)initWithFrame:(NSRect)frameRect { + if ((self = [super initWithFrame: frameRect])) { + [self _init]; + } + return self; +} + +-(id)initWithCoder:(NSCoder *)coder { + if ((self = [super initWithCoder: coder])) { + [self _init]; + } + return self; +} + +#if 0 +-(NSString *)stringValue { + return [super stringValue]; +} +#endif + +-(id)objectValue { + return _value; +} + +-(void)setStringValue:(NSString *)stringValue { + [super setStringValue: stringValue]; + if (_value && [[_value menuTitle] isEqualToString: stringValue] == NO) + _value = nil; + + // todo -- search for a matching item, update text color. +} + +-(void)setObjectValue:(id)objectValue { + if (_value == objectValue) return; + if (![objectValue conformsToProtocol: @protocol(AutocompleteItem)]) { + _value = nil; + [super setStringValue: @""]; + return; + } + _value = objectValue; + if (!_value) [super setStringValue: @""]; // + else { + [super setStringValue: [_value menuTitle]]; + NSArray *array = [_autocompleteDelegate autocomplete: self completionsForItem: _value]; + [_menuView setItems: array]; + } +} + +-(BOOL)valid { + return _value != nil; +} + + +-(void)hideSuggestions: (id)sender { + + if (![_panel isVisible]) return; + + NSWindow *window = [self window]; + + [window removeChildWindow: _panel]; + [_panel orderOut: sender]; + +} + +-(void)showSuggestions: (id)sender { + + if ([_panel isVisible]) return; + NSWindow *window = [self window]; + + NSRect wFrame = [_panel frame]; + NSRect vFrame = [self frame]; + + NSRect rect = { .origin = vFrame.origin, .size = wFrame.size }; + rect = [window convertRectToScreen:rect]; + + rect.origin.y -= wFrame.size.height + 4; + rect.size.width = MAX(vFrame.size.width, _minWidth); + // todo - min width option. + [_panel setFrame: rect display: YES]; + + //[_panel setFrameOrigin: rect.origin]; + [window addChildWindow: _panel ordered: NSWindowAbove]; +} + +-(void)updateSuggestions { + + if (!_autocompleteDelegate) return; + NSString *needle = [self stringValue]; + + NSArray *items = [_autocompleteDelegate autocomplete: self completionsForString: needle]; + + [_menuView setItems: items]; + if ([items count]) { + [self showSuggestions: nil]; + } else { + [self hideSuggestions: nil]; + } +} + +-(void)invalidate { + + if (!_autocompleteDelegate) return; + + NSArray *items = nil; + + /* if there is an object value, try to retain it. */ + if (_value) { + + [_menuView reset]; + items = [_autocompleteDelegate autocomplete: self completionsForItem: _value]; + if (items) { + [_menuView setItems: items]; + return; + } + _value = nil; + [self invoke]; + } + NSString *needle = [self stringValue]; + + if ([needle length]) { + _dirty = YES; + } + // if only 1 match, auto-set value? + items = [_autocompleteDelegate autocomplete: self completionsForString: needle]; + [self fixTextColor: _editing]; + [_menuView setItems: items]; +} + + +// prevent action messages from the search field/cell. +-(BOOL)sendAction:(SEL)action to:(id)target { + if (action == [self action] && target == [self target]) return NO; + return [super sendAction: action to: target]; + +} + +-(void)invoke { + _dirty = NO; + [super sendAction: [self action] to: [self target]]; +} + +@end + + +@implementation AutocompleteControl (SearchField) + +-(void)fixTextColor: (BOOL)editing { + NSColor *color = editing || _value ? [NSColor controlTextColor] : [NSColor systemRedColor]; + [self setTextColor: color]; +} + +- (void)controlTextDidChange:(NSNotification *)notification { + NSLog(@"controlTextDidChange"); + + if (_value) { + _dirty = YES; + _value = nil; + } + + NSString *s = [self stringValue]; + if ([s length]) { + [self updateSuggestions]; + } else { + _dirty = YES; + _value = nil; + [_menuView reset]; + [_menuView setItems: nil]; + [self hideSuggestions: nil]; + [self invoke]; + } +} +- (void)controlTextDidBeginEditing:(NSNotification *)obj { + NSLog(@"controlTextDidBeginEditing"); + + _editing = YES; + _dirty = NO; + [self fixTextColor: YES]; +} + + + +- (void)controlTextDidEndEditing:(NSNotification *)obj { + NSLog(@"controlTextDidEndEditing"); + + _editing = NO; + + [self hideSuggestions: nil]; + + if (_dirty) { + _value = nil; + [self invoke]; + } + [self fixTextColor: NO]; +} + + +-(BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)fieldEditor { + return YES; +} + +-(BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor { + return YES; +} + +-(void)selectItem:(id)item withSelector:(SEL)selector { + + // for newline/mousedown, will still retain focus after updating + // so we need to invalidate the value if it's edited further. + if (selector == @selector(insertNewline:) || selector == @selector(mouseDown:) || selector == @selector(insertTab:)) { + + _value = item; + NSString *str = [item menuTitle]; + + [super setStringValue: str]; + + [self hideSuggestions: nil]; + + NSText *fieldEditor = [self currentEditor]; + //[fieldEditor setSelectedRange: NSMakeRange([str length], 0)]; + [fieldEditor setSelectedRange: NSMakeRange(0, [str length])]; + + [self invoke]; + + // need to invalidate the menu so it reloads + + +#if 0 + NSArray *array = [_autocompleteDelegate autocomplete: self completionsForItem: _value]; + [_menuView setItems: array]; +#else + [_menuView setItems: nil]; +#endif + + NSLog(@"selectItem:withSelector:"); + } +} + + +- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector { + + if (commandSelector == @selector(moveUp:)) { + //[self showSuggestions: nil]; + if ([_panel isVisible]) { + [_menuView moveUp: textView]; + } else { + [self updateSuggestions]; + } + return YES; + } + + if (commandSelector == @selector(moveDown:)) { + //[self showSuggestions: nil]; + if ([_panel isVisible]) { + [_menuView moveDown: textView]; + } else { + [self updateSuggestions]; + } + return YES; + } + + if (commandSelector == @selector(insertNewline:)) { + if ([_panel isVisible]) + [_menuView insertNewline: textView]; + return YES; + } + + if (commandSelector == @selector(insertTab:)) { + if ([_panel isVisible]) + [_menuView insertTab: textView]; + return NO; + } + + if (commandSelector == @selector(complete:)) { + if ([_panel isVisible]) { + [self hideSuggestions: nil]; + } else { + [self updateSuggestions]; + } + return YES; + } + + + // esc ? + // if panel open, hide + // if panel closed, delete. + if (commandSelector == @selector(cancelOperation:)) { + + if ([_panel isVisible]) { + [self hideSuggestions: nil]; + } else { + _value = nil; + [super setStringValue: @""]; + + [self hideSuggestions: nil]; + [_menuView reset]; + //[_menuView setItems: _completions]; + // sigh... + #if 0 + NSArray *items =[_autocompleteDelegate autocomplete: self completionsForString: @""]; + [_menuView setItems: items]; + #endif + [self invoke]; + } + return YES; + } + + //NSLog(@"%@", NSStringFromSelector(commandSelector)); + return NO; +} + + +@end + + +@interface ACMenuView () { + id _value; + NSInteger _index; + NSInteger _count; + NSArray> *_items; + NSTrackingArea *_trackingArea; + NSColor *_backgroundColor; + NSColor *_selectedColor; + BOOL _tracking; + BOOL _clipped; +} +@end + +@implementation ACMenuView + +#define MENU_FONT_SIZE 11 +#define MENU_HEIGHT 14 +#define MARGIN_TOP 0 //6 +#define MARGIN_BOTTOM 0 //6 +#define INDENT 7 +#define HEADER_INDENT 7 +#define MAX_DISPLAY_ITEMS 16 + + +-(void)_init { + _backgroundColor = [NSColor windowBackgroundColor]; + _selectedColor = [NSColor selectedContentBackgroundColor]; + + NSTrackingAreaOptions options = NSTrackingMouseMoved | NSTrackingMouseEnteredAndExited | NSTrackingInVisibleRect | NSTrackingActiveInActiveApp; + _trackingArea = [[NSTrackingArea alloc] initWithRect: NSZeroRect + options: options + owner: self + userInfo: nil]; + + [self addTrackingArea: _trackingArea]; +} + +-(id)initWithCoder:(NSCoder *)coder { + if ((self = [super initWithCoder: coder])) { + [self _init]; + } + return self; + +} + +-(BOOL)isFlipped { + return YES; +} + +-(BOOL)acceptsFirstMouse:(NSEvent *)event { + return YES; +} + +static CGFloat HeightForItems(NSUInteger count) { + return count * MENU_HEIGHT; +} + +-(NSSize)intrinsicContentSize { + return NSMakeSize(NSViewNoIntrinsicMetric, _count * MENU_HEIGHT + MARGIN_TOP + MARGIN_BOTTOM); +} + +- (NSSize)sizeThatFits:(NSSize)size { + size.height = _count * MENU_HEIGHT + MARGIN_TOP + MARGIN_BOTTOM; + return size; +} + +- (void)sizeToFit { + NSSize size = [self frame].size; + size.height = _count * MENU_HEIGHT + MARGIN_TOP + MARGIN_BOTTOM; + [self setFrameSize: size]; + [self setNeedsDisplay: YES]; +} + +-(void)reset { + [self invalidateRow: _index]; + _index = -1; + _value = nil; + _items = nil; +} + +-(void)setItems:(NSArray *)items { + if (_items == items) return; + _items = [items copy]; + _index = -1; + _count = [items count]; + + + if (!_items) return; + + // also check enabled status.... + if (_value) { + _index = [_items indexOfObject: _value]; + if (_index == NSNotFound) { + _index = -1; + _value = nil; + } + } + + // if only 1 entry, auto-select it. + if (!_value) { + NSInteger count = -1; + for (id item in _items) { + ++count; + if ([item menuIsHeader]) continue; + if (_value) { + _value = nil; + _index = -1; + break; + } + _value = item; + _index = count; + } + } + + + + NSInteger displayCount = MIN(_count, MAX_DISPLAY_ITEMS); + CGFloat newHeight = HeightForItems(displayCount) + 8 ; // 4px top/bottom + NSWindow *window = [self window]; + NSRect wFrame = [window frame]; + + NSRect contentRect = [[[self enclosingScrollView] contentView] frame]; + + //NSSize size = [self intrinsicContentSize]; + //NSInteger minWidth = [_parent minWidth]; + //size.width = MAX(wFrame.size.width, minWidth); + //size.height += 8; + + CGFloat delta = wFrame.size.height - newHeight; + + wFrame.origin.y += delta; + wFrame.size.height = newHeight; + + _clipped = (_count > displayCount); + + [self setFrameSize: NSMakeSize(contentRect.size.width /*- 15.0*/, HeightForItems(_count))]; + [self setNeedsDisplay: YES]; + [window setFrame: wFrame display: YES]; + + if (_value) { + [self scrollToRow: _index position: ScrollToCenter force: NO]; + } else { + [self scrollToRow: 0 position: ScrollToTop force: YES]; + } + + //[self sizeToFit]; + //[[self window] setContentSize: [self frame].size]; + + //NSLog(@"%@", NSStringFromRect(wFrame)); +} + +-(id)itemAtPoint: (NSPoint)point indexPtr: (NSInteger *)indexPtr { + + NSInteger index = floor(point.y / MENU_HEIGHT); + if (index < 0 || index >= _count) return nil; + if (indexPtr) *indexPtr = index; + return [_items objectAtIndex: index]; +} + +enum { + ScrollToTop, + ScrollToBottom, + ScrollToCenter, +}; + +-(void)scrollToRow: (NSInteger)row position: (unsigned)position force: (BOOL)force { + if (row < 0) return; + if (!_clipped) return; + + NSScrollView *scrollView = [self enclosingScrollView]; + NSClipView *clipView = [scrollView contentView]; + + NSRect visibleRect = [self visibleRect]; + if (!force) { + NSRect mRect = NSMakeRect(0, row * MENU_HEIGHT, 1 , MENU_HEIGHT); + if (NSContainsRect(visibleRect, mRect)) return; + } + + NSInteger topRow = row; + switch (position) { + case ScrollToTop: + break; + case ScrollToBottom: + topRow -= MAX_DISPLAY_ITEMS -1; + break; + case ScrollToCenter: + topRow -= MAX_DISPLAY_ITEMS/2 - 1; + break; + } + if (topRow < 0) topRow = 0; + if (topRow > _count - MAX_DISPLAY_ITEMS) + topRow = _count - MAX_DISPLAY_ITEMS; + NSPoint point = NSMakePoint(0, topRow * MENU_HEIGHT); + + //[self scrollClipView: clipView toPoint: point]; + [clipView scrollToPoint: point]; + [scrollView reflectScrolledClipView: clipView]; + +} + + +-(void)moveUp:(id)sender { + if (_count == 0 || _index <= 0) return; + + NSInteger index = 0; + id value = nil; + for (index = _index - 1; index >= 0; --index) { + value = [_items objectAtIndex: index]; + if ([value menuIsHeader]) continue; + if (![value menuEnabled]) continue; + break; + } + if (index < 0) return; + if (index == _index) return; + [self invalidateRow: _index]; + [self invalidateRow: index]; + + _index = index; + _value = value; + [self scrollToRow: index position: ScrollToTop force: NO]; + [_parent selectItem: _value withSelector: _cmd]; +} + +-(void)moveDown:(id)sender { + + // _index -1 selects first item. + if (_count == 0 || _index == _count - 1) return; + + NSInteger index = 0; + id value = nil; + for (index = _index + 1; index < _count ; ++index) { + value = [_items objectAtIndex: index]; + if ([value menuIsHeader]) continue; + if (![value menuEnabled]) continue; + break; + } + if (index == _count) return; + if (index == _index) return; + [self invalidateRow: _index]; + [self invalidateRow: index]; + + _index = index; + _value = value; + + [self scrollToRow: index position: ScrollToBottom force: NO]; + [_parent selectItem: _value withSelector: _cmd]; +} + +-(void)insertNewline:(id)sender { + if (_value) { + [_parent selectItem: _value withSelector: _cmd]; + } +} + +-(void)insertTab:(id)sender { + // if only one option, autocomplete? + if (_value) { + [_parent selectItem: _value withSelector: _cmd]; + } +} + +-(void)mouseMoved:(NSEvent *)event { + //NSLog(@"mouse moved"); + + if (!_tracking) return; + + NSPoint p = [event locationInWindow]; + p = [self convertPoint: p fromView: nil]; + + + NSInteger index; + id value = [self itemAtPoint: p indexPtr: &index]; + if (!value) return; + if (index == _index) return; + if ([value menuIsHeader]) return; + if (![value menuEnabled]) return; + + [self invalidateRow: _index]; + [self invalidateRow: index]; + + _index = index; + _value = value; + [_parent selectItem: _value withSelector: _cmd]; +} +-(void)mouseDown:(NSEvent *)event { + if (!_tracking) return; + + NSPoint p = [event locationInWindow]; + p = [self convertPoint: p fromView: nil]; + + NSInteger index; + id value = [self itemAtPoint: p indexPtr: &index]; + if (!value) return; + if (index != _index) { + if ([value menuIsHeader]) return; + if (![value menuEnabled]) return; + + [self invalidateRow: _index]; + [self invalidateRow: index]; + + _index = index; + _value = value; + } + + [_parent selectItem: _value withSelector: _cmd]; +} + +-(void)mouseEntered:(NSEvent *)event { + //NSLog(@"mouse entered"); + _tracking = YES; + +} +-(void)mouseExited:(NSEvent *)event { + //NSLog(@"mouse exited"); + + _tracking = NO; +} + +-(void)invalidateRow:(NSInteger)row { + if (row < 0 || row >= _count) return; + + NSRect r = NSZeroRect; + NSRect bounds = [self bounds]; + + r.size.width = bounds.size.width; + r.size.height = MENU_HEIGHT; + r.origin.y = MENU_HEIGHT * row + MARGIN_TOP; + //NSLog(@"Invalidating %ld - %@", row, NSStringFromRect(r)); + [self setNeedsDisplayInRect: r]; +} + + +static void DrawString(NSString *str, NSDictionary *attr, CGRect rect) { + + NSSize size = [str sizeWithAttributes: attr]; + if (size.width <= rect.size.width) { + [str drawInRect: rect withAttributes: attr]; + return; + } + NSMutableString *mstr = [str mutableCopy]; + // binary search is probably the best way to handle it :/ + NSInteger l = [mstr length]; + while (l > 2) { + [mstr replaceCharactersInRange: NSMakeRange(l-2, 2) withString: @"…"]; + --l; + size = [mstr sizeWithAttributes: attr]; + if (size.width <= rect.size.width) { + [mstr drawInRect: rect withAttributes: attr]; + return; + } + } +} + +-(void)drawItem: (id)item inRect: (NSRect)rect { + NSColor *textColor = [NSColor textColor]; + if (!item) return; + + if (item == _value) { + textColor = [NSColor selectedMenuItemTextColor]; + [_selectedColor setFill]; + NSRectFill(rect); + } + NSString *string = [item menuTitle]; + + if ([item menuIsHeader]) { + textColor = [NSColor secondaryLabelColor]; + NSDictionary *attr = @{ + NSForegroundColorAttributeName: textColor, + NSFontAttributeName: [NSFont systemFontOfSize: MENU_FONT_SIZE], // [NSFont boldSystemFontOfSize: 13], + }; + NSRect r = NSInsetRect(rect, HEADER_INDENT, 0); + DrawString(string, attr, r); + } else { + + NSDictionary *attr = @{ + NSForegroundColorAttributeName: textColor, + NSFontAttributeName: [NSFont systemFontOfSize: MENU_FONT_SIZE], + }; + NSRect r = NSInsetRect(rect, INDENT, 0); + r.origin.x += INDENT; + r.size.width -= INDENT; + DrawString(string, attr, r); + } +} + +- (void)drawRect:(NSRect)dirtyRect { + [super drawRect:dirtyRect]; + + NSRect r = [self bounds]; + NSInteger begin = floor((NSMinY(dirtyRect) - MARGIN_TOP) / MENU_HEIGHT); + NSInteger end = ceil((NSMaxY(dirtyRect) - MARGIN_TOP) / MENU_HEIGHT); + if (begin < 0) begin = 0; + if (end > _count) end = _count; + + + r.origin.y = MENU_HEIGHT * begin + MARGIN_TOP; + r.size.height = MENU_HEIGHT; + for (NSInteger index = begin; index < end; ++index) { + id item = [_items objectAtIndex: index]; + [self drawItem: item inRect: r]; + r.origin.y += MENU_HEIGHT; + } +} + +@end + + +/* custom scroller that doesn't draw a background. */ +@interface ACScroller : NSScroller +@end + +@implementation ACScroller + + +-(void)drawRect:(NSRect)dirtyRect { + [[NSColor windowBackgroundColor] set]; + NSRectFill(dirtyRect); + [self drawKnob]; +} + +@end + + +@interface ACPanel : NSPanel +@end + +@implementation ACPanel + +/* needed to prevent the pop-up child window from being moved when offscreen. */ +- (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen { + return frameRect; +} + + +@end diff --git a/Ample/Base.lproj/Autocomplete.xib b/Ample/Base.lproj/Autocomplete.xib new file mode 100644 index 0000000..ea46068 --- /dev/null +++ b/Ample/Base.lproj/Autocomplete.xib @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Ample/Base.lproj/LaunchWindow.xib b/Ample/Base.lproj/LaunchWindow.xib index 494771e..5299d12 100644 --- a/Ample/Base.lproj/LaunchWindow.xib +++ b/Ample/Base.lproj/LaunchWindow.xib @@ -14,6 +14,7 @@ + @@ -372,7 +373,7 @@ DQ - + @@ -409,6 +410,18 @@ DQ + + + + + + + + + + + + diff --git a/Ample/LaunchWindowController.m b/Ample/LaunchWindowController.m index 97622cb..34adccd 100644 --- a/Ample/LaunchWindowController.m +++ b/Ample/LaunchWindowController.m @@ -13,8 +13,11 @@ #import "MachineViewController.h" #import "LogWindowController.h" +#import "AutocompleteControl.h" +#import "SoftwareList.h" #include +#include static NSString *kMyContext = @"kMyContext"; static NSString *kContextMachine = @"kContextMachine"; @@ -57,8 +60,16 @@ static NSString *kContextMachine = @"kContextMachine"; @property NSInteger mameWindowMode; +@property (weak) IBOutlet AutocompleteControl *softwareListControl; +@property SoftwareSet *softwareSet; +@property Software *software; @end +@interface LaunchWindowController (SoftwareList) + +-(void)updateSoftwareList; + +@end @implementation LaunchWindowController @@ -94,6 +105,7 @@ static NSString *kContextMachine = @"kContextMachine"; @"mameVGM", @"mameVGMPath", @"mameShareDirectory", @"mameBGFX", @"mameBackend", @"mameEffects", + @"software", ]; for (NSString *key in keys) { @@ -108,6 +120,10 @@ static NSString *kContextMachine = @"kContextMachine"; [_machineViewController addObserver: self forKeyPath: @"machine" options: 0 context: (__bridge void * _Nullable)kContextMachine]; + + [_softwareListControl setMinWidth: 250]; + [_softwareListControl setHidden: YES]; + [self buildCommandLine]; } @@ -120,6 +136,7 @@ static NSString *kContextMachine = @"kContextMachine"; NSString *machine = [_machineViewController machine]; [self setMameMachine: machine]; [_slotController setMachine: machine]; + [self updateSoftwareList]; [self buildCommandLine]; } else { [super observeValueForKeyPath: keyPath ofObject: object change: change context: context]; @@ -278,6 +295,14 @@ static NSString *ShellQuote(NSString *s) { //[argv addObject: @"mame"]; [argv addObject: _mameMachine]; + + if (_software) { + // todo -- need to include source as well. + NSString *name = [_software name]; + if (![_softwareSet nameIsUnique: name]) + name = [_software fullName]; + [argv addObject: name]; + } // -confirm_quit? [argv addObject: @"-skip_gameinfo"]; @@ -483,3 +508,30 @@ static NSString *ShellQuote(NSString *s) { @end + +@implementation LaunchWindowController (SoftwareList) + +-(void)updateSoftwareList { + + _softwareSet = [SoftwareSet softwareSetForMachine: _mameMachine]; + + [_softwareListControl setAutocompleteDelegate: _softwareSet]; + + if (_softwareSet) { + [_softwareListControl invalidate]; + [_softwareListControl setHidden: NO]; + } else { + _software = nil; + [_softwareListControl setHidden: YES]; + } +} + + +- (IBAction)softwareChanged:(id)sender { + + id o = [(NSControl *)sender objectValue]; + NSLog(@"%@", o); + [self setSoftware: o]; +} + +@end diff --git a/Ample/Resources/ace100.plist b/Ample/Resources/ace100.plist index e472a83..67a1adb 100644 --- a/Ample/Resources/ace100.plist +++ b/Ample/Resources/ace100.plist @@ -5944,7 +5944,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2 + apple2_flop_misc.xml apple2_cass.xml diff --git a/Ample/Resources/ace1000.plist b/Ample/Resources/ace1000.plist index 22c5cd8..a74ccf4 100644 --- a/Ample/Resources/ace1000.plist +++ b/Ample/Resources/ace1000.plist @@ -5884,7 +5884,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2P + apple2_flop_misc.xml apple2_cass.xml diff --git a/Ample/Resources/albert.plist b/Ample/Resources/albert.plist index 6132d87..f5bc6e6 100644 --- a/Ample/Resources/albert.plist +++ b/Ample/Resources/albert.plist @@ -5884,7 +5884,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2P + apple2_flop_misc.xml apple2_cass.xml diff --git a/Ample/Resources/am100.plist b/Ample/Resources/am100.plist index 23b4525..814d104 100644 --- a/Ample/Resources/am100.plist +++ b/Ample/Resources/am100.plist @@ -5884,7 +5884,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2P + apple2_flop_misc.xml apple2_cass.xml diff --git a/Ample/Resources/am64.plist b/Ample/Resources/am64.plist index 0c08d12..086fd71 100644 --- a/Ample/Resources/am64.plist +++ b/Ample/Resources/am64.plist @@ -5884,7 +5884,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2P + apple2_flop_misc.xml apple2_cass.xml diff --git a/Ample/Resources/apple2.plist b/Ample/Resources/apple2.plist index 16dde5d..88056a7 100644 --- a/Ample/Resources/apple2.plist +++ b/Ample/Resources/apple2.plist @@ -5944,7 +5944,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2 + apple2_flop_misc.xml apple2_cass.xml diff --git a/Ample/Resources/apple2c.plist b/Ample/Resources/apple2c.plist index 31cc230..0af9e60 100644 --- a/Ample/Resources/apple2c.plist +++ b/Ample/Resources/apple2c.plist @@ -485,7 +485,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2C + apple2_flop_misc.xml diff --git a/Ample/Resources/apple2cp.plist b/Ample/Resources/apple2cp.plist index 29fc7b5..7f040fa 100644 --- a/Ample/Resources/apple2cp.plist +++ b/Ample/Resources/apple2cp.plist @@ -447,7 +447,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2C + apple2_flop_misc.xml diff --git a/Ample/Resources/apple2e.plist b/Ample/Resources/apple2e.plist index d5880fe..3d66057 100644 --- a/Ample/Resources/apple2e.plist +++ b/Ample/Resources/apple2e.plist @@ -7672,7 +7672,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2E + apple2_flop_misc.xml diff --git a/Ample/Resources/apple2ee.plist b/Ample/Resources/apple2ee.plist index 678d2c9..ea71e6e 100644 --- a/Ample/Resources/apple2ee.plist +++ b/Ample/Resources/apple2ee.plist @@ -7672,7 +7672,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2EE + apple2_flop_misc.xml diff --git a/Ample/Resources/apple2eefr.plist b/Ample/Resources/apple2eefr.plist index bcdd8c6..6ef27d0 100644 --- a/Ample/Resources/apple2eefr.plist +++ b/Ample/Resources/apple2eefr.plist @@ -7672,7 +7672,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2EE + apple2_flop_misc.xml diff --git a/Ample/Resources/apple2ees.plist b/Ample/Resources/apple2ees.plist index 29b880b..fc181ed 100644 --- a/Ample/Resources/apple2ees.plist +++ b/Ample/Resources/apple2ees.plist @@ -7672,7 +7672,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2E + apple2_flop_misc.xml diff --git a/Ample/Resources/apple2eeuk.plist b/Ample/Resources/apple2eeuk.plist index 1fc1daa..20990b2 100644 --- a/Ample/Resources/apple2eeuk.plist +++ b/Ample/Resources/apple2eeuk.plist @@ -7672,7 +7672,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2EE + apple2_flop_misc.xml diff --git a/Ample/Resources/apple2ep.plist b/Ample/Resources/apple2ep.plist index 33fb7f0..3b26acb 100644 --- a/Ample/Resources/apple2ep.plist +++ b/Ample/Resources/apple2ep.plist @@ -7672,7 +7672,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2E + apple2_flop_misc.xml diff --git a/Ample/Resources/apple2euk.plist b/Ample/Resources/apple2euk.plist index bab48fe..dd89c1d 100644 --- a/Ample/Resources/apple2euk.plist +++ b/Ample/Resources/apple2euk.plist @@ -7672,7 +7672,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2E + apple2_flop_misc.xml diff --git a/Ample/Resources/apple2gs.plist b/Ample/Resources/apple2gs.plist index 34b7fb3..7535a41 100644 --- a/Ample/Resources/apple2gs.plist +++ b/Ample/Resources/apple2gs.plist @@ -7481,7 +7481,12 @@ apple2gs.xml apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2GS + apple2_flop_misc.xml diff --git a/Ample/Resources/apple2gsr0.plist b/Ample/Resources/apple2gsr0.plist index bf9ef19..6ff0cd6 100644 --- a/Ample/Resources/apple2gsr0.plist +++ b/Ample/Resources/apple2gsr0.plist @@ -7521,7 +7521,12 @@ apple2gs.xml apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2GS + apple2_flop_misc.xml diff --git a/Ample/Resources/apple2gsr1.plist b/Ample/Resources/apple2gsr1.plist index 34e3760..b8dc6ef 100644 --- a/Ample/Resources/apple2gsr1.plist +++ b/Ample/Resources/apple2gsr1.plist @@ -7521,7 +7521,12 @@ apple2gs.xml apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2GS + apple2_flop_misc.xml diff --git a/Ample/Resources/apple2jp.plist b/Ample/Resources/apple2jp.plist index 4ebdeb4..caea2bc 100644 --- a/Ample/Resources/apple2jp.plist +++ b/Ample/Resources/apple2jp.plist @@ -5884,7 +5884,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2P + apple2_flop_misc.xml apple2_cass.xml diff --git a/Ample/Resources/apple2p.plist b/Ample/Resources/apple2p.plist index 2af561a..36bd4ef 100644 --- a/Ample/Resources/apple2p.plist +++ b/Ample/Resources/apple2p.plist @@ -5884,7 +5884,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2P + apple2_flop_misc.xml apple2_cass.xml diff --git a/Ample/Resources/basis108.plist b/Ample/Resources/basis108.plist index 2f020f9..59e7860 100644 --- a/Ample/Resources/basis108.plist +++ b/Ample/Resources/basis108.plist @@ -5944,7 +5944,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2 + apple2_flop_misc.xml apple2_cass.xml diff --git a/Ample/Resources/cec2000.plist b/Ample/Resources/cec2000.plist index fe5aa6f..9d95919 100644 --- a/Ample/Resources/cec2000.plist +++ b/Ample/Resources/cec2000.plist @@ -6609,7 +6609,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2E + apple2_flop_misc.xml cecflop.xml diff --git a/Ample/Resources/cece.plist b/Ample/Resources/cece.plist index 006383d..8c62859 100644 --- a/Ample/Resources/cece.plist +++ b/Ample/Resources/cece.plist @@ -6609,7 +6609,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2E + apple2_flop_misc.xml cecflop.xml diff --git a/Ample/Resources/cecg.plist b/Ample/Resources/cecg.plist index e220c17..a21f5f7 100644 --- a/Ample/Resources/cecg.plist +++ b/Ample/Resources/cecg.plist @@ -6609,7 +6609,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2E + apple2_flop_misc.xml cecflop.xml diff --git a/Ample/Resources/ceci.plist b/Ample/Resources/ceci.plist index f3ff853..49fd5cd 100644 --- a/Ample/Resources/ceci.plist +++ b/Ample/Resources/ceci.plist @@ -6609,7 +6609,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2E + apple2_flop_misc.xml cecflop.xml diff --git a/Ample/Resources/cecm.plist b/Ample/Resources/cecm.plist index c0da0b3..11c3156 100644 --- a/Ample/Resources/cecm.plist +++ b/Ample/Resources/cecm.plist @@ -6609,7 +6609,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2E + apple2_flop_misc.xml cecflop.xml diff --git a/Ample/Resources/craft2p.plist b/Ample/Resources/craft2p.plist index d53fb24..b3dbfb7 100644 --- a/Ample/Resources/craft2p.plist +++ b/Ample/Resources/craft2p.plist @@ -5884,7 +5884,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2P + apple2_flop_misc.xml apple2_cass.xml diff --git a/Ample/Resources/dodo.plist b/Ample/Resources/dodo.plist index dd6591e..881af36 100644 --- a/Ample/Resources/dodo.plist +++ b/Ample/Resources/dodo.plist @@ -5884,7 +5884,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2P + apple2_flop_misc.xml apple2_cass.xml diff --git a/Ample/Resources/elppa.plist b/Ample/Resources/elppa.plist index 42ac851..f51311c 100644 --- a/Ample/Resources/elppa.plist +++ b/Ample/Resources/elppa.plist @@ -5884,7 +5884,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2P + apple2_flop_misc.xml apple2_cass.xml diff --git a/Ample/Resources/hkc8800a.plist b/Ample/Resources/hkc8800a.plist index 867c64c..45585f4 100644 --- a/Ample/Resources/hkc8800a.plist +++ b/Ample/Resources/hkc8800a.plist @@ -5884,7 +5884,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2P + apple2_flop_misc.xml apple2_cass.xml diff --git a/Ample/Resources/ivelultr.plist b/Ample/Resources/ivelultr.plist index d58ac41..5ecb0f5 100644 --- a/Ample/Resources/ivelultr.plist +++ b/Ample/Resources/ivelultr.plist @@ -5884,7 +5884,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2P + apple2_flop_misc.xml apple2_cass.xml diff --git a/Ample/Resources/las128e2.plist b/Ample/Resources/las128e2.plist index 7cb4f67..d309447 100644 --- a/Ample/Resources/las128e2.plist +++ b/Ample/Resources/las128e2.plist @@ -445,7 +445,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2C + apple2_flop_misc.xml diff --git a/Ample/Resources/las128ex.plist b/Ample/Resources/las128ex.plist index 4fb709a..7ebf68d 100644 --- a/Ample/Resources/las128ex.plist +++ b/Ample/Resources/las128ex.plist @@ -5157,7 +5157,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2C + apple2_flop_misc.xml diff --git a/Ample/Resources/laser128.plist b/Ample/Resources/laser128.plist index b20c100..7a13520 100644 --- a/Ample/Resources/laser128.plist +++ b/Ample/Resources/laser128.plist @@ -5157,7 +5157,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2C + apple2_flop_misc.xml diff --git a/Ample/Resources/laser2c.plist b/Ample/Resources/laser2c.plist index 2c13064..628becf 100644 --- a/Ample/Resources/laser2c.plist +++ b/Ample/Resources/laser2c.plist @@ -5884,7 +5884,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2P + apple2_flop_misc.xml apple2_cass.xml diff --git a/Ample/Resources/maxxi.plist b/Ample/Resources/maxxi.plist index 0cd72ac..77329da 100644 --- a/Ample/Resources/maxxi.plist +++ b/Ample/Resources/maxxi.plist @@ -5884,7 +5884,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2P + apple2_flop_misc.xml apple2_cass.xml diff --git a/Ample/Resources/microeng.plist b/Ample/Resources/microeng.plist index 755a7b4..6c5318b 100644 --- a/Ample/Resources/microeng.plist +++ b/Ample/Resources/microeng.plist @@ -5884,7 +5884,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2P + apple2_flop_misc.xml apple2_cass.xml diff --git a/Ample/Resources/mprof3.plist b/Ample/Resources/mprof3.plist index ba64134..adc58ca 100644 --- a/Ample/Resources/mprof3.plist +++ b/Ample/Resources/mprof3.plist @@ -7672,7 +7672,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2E + apple2_flop_misc.xml diff --git a/Ample/Resources/prav82.plist b/Ample/Resources/prav82.plist index 9c2070a..52a1fe5 100644 --- a/Ample/Resources/prav82.plist +++ b/Ample/Resources/prav82.plist @@ -5884,7 +5884,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2P + apple2_flop_misc.xml apple2_cass.xml diff --git a/Ample/Resources/prav8c.plist b/Ample/Resources/prav8c.plist index 0d74352..1ee037d 100644 --- a/Ample/Resources/prav8c.plist +++ b/Ample/Resources/prav8c.plist @@ -7672,7 +7672,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2E + apple2_flop_misc.xml diff --git a/Ample/Resources/prav8m.plist b/Ample/Resources/prav8m.plist index 634c20f..c7f20f0 100644 --- a/Ample/Resources/prav8m.plist +++ b/Ample/Resources/prav8m.plist @@ -5884,7 +5884,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2P + apple2_flop_misc.xml apple2_cass.xml diff --git a/Ample/Resources/roms.plist b/Ample/Resources/roms.plist index 2654927..e57ae59 100644 --- a/Ample/Resources/roms.plist +++ b/Ample/Resources/roms.plist @@ -15,6 +15,7 @@ a2ap16 a2ap16a a2aplcrd + a2booti a2bufgrapplerplus a2bufgrapplerplusa a2cffa02 @@ -154,6 +155,7 @@ macsefd maxxi microeng + mockingboardd mprof3 nb_48gc nb_824gc diff --git a/Ample/Resources/space84.plist b/Ample/Resources/space84.plist index d6ba7b3..842a9bb 100644 --- a/Ample/Resources/space84.plist +++ b/Ample/Resources/space84.plist @@ -5884,7 +5884,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2P + apple2_flop_misc.xml apple2_cass.xml diff --git a/Ample/Resources/spectred.plist b/Ample/Resources/spectred.plist index 93003bd..0ba6bcf 100644 --- a/Ample/Resources/spectred.plist +++ b/Ample/Resources/spectred.plist @@ -7672,7 +7672,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2E + apple2_flop_misc.xml diff --git a/Ample/Resources/tk3000.plist b/Ample/Resources/tk3000.plist index 1d4546e..3d3e7bf 100644 --- a/Ample/Resources/tk3000.plist +++ b/Ample/Resources/tk3000.plist @@ -7672,7 +7672,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2E + apple2_flop_misc.xml diff --git a/Ample/Resources/uniap2en.plist b/Ample/Resources/uniap2en.plist index 6c1cb3d..8ee297b 100644 --- a/Ample/Resources/uniap2en.plist +++ b/Ample/Resources/uniap2en.plist @@ -5884,7 +5884,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2P + apple2_flop_misc.xml apple2_cass.xml diff --git a/Ample/Resources/uniap2pt.plist b/Ample/Resources/uniap2pt.plist index bebe383..258f007 100644 --- a/Ample/Resources/uniap2pt.plist +++ b/Ample/Resources/uniap2pt.plist @@ -5884,7 +5884,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2P + apple2_flop_misc.xml apple2_cass.xml diff --git a/Ample/Resources/uniap2ti.plist b/Ample/Resources/uniap2ti.plist index 78758d6..cb3001f 100644 --- a/Ample/Resources/uniap2ti.plist +++ b/Ample/Resources/uniap2ti.plist @@ -5884,7 +5884,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2P + apple2_flop_misc.xml apple2_cass.xml diff --git a/Ample/Resources/zijini.plist b/Ample/Resources/zijini.plist index bc873be..fae7959 100644 --- a/Ample/Resources/zijini.plist +++ b/Ample/Resources/zijini.plist @@ -6609,7 +6609,12 @@ software apple2_flop_clcracked.xml - apple2_flop_orig.xml + + name + apple2_flop_orig.xml + filter + A2E + apple2_flop_misc.xml cecflop.xml diff --git a/Ample/SoftwareList.h b/Ample/SoftwareList.h index 770af68..6f86883 100644 --- a/Ample/SoftwareList.h +++ b/Ample/SoftwareList.h @@ -10,20 +10,37 @@ #define SoftwareList_h #import +#import "AutocompleteControl.h" -@interface SoftwareList : NSObject + +@interface SoftwareList : NSObject @property NSString *name; @property NSString *title; @property NSArray *items; + +-(SoftwareList *)filter: (NSString *)filter; + @end -@interface Software : NSObject +@interface Software : NSObject @property NSString *name; @property NSString *title; +@property NSString *compatibility; +@property NSString *list; + +-(NSString *)fullName; + +@end + +@interface SoftwareSet : NSObject + ++(instancetype)softwareSetForMachine: (NSString *)machine; +-(BOOL)nameIsUnique: (NSString *)name; + @end -NSArray *SoftwareListForMachine(NSString *machine); +//NSArray *SoftwareListForMachine(NSString *machine); #endif /* SoftwareList_h */ diff --git a/Ample/SoftwareList.m b/Ample/SoftwareList.m index 85cb3fc..e19562f 100644 --- a/Ample/SoftwareList.m +++ b/Ample/SoftwareList.m @@ -7,22 +7,174 @@ // #import -#include "Ample.h" +#include +#include "Ample.h" #import "SoftwareList.h" @implementation Software + +-(BOOL)filter: (NSString *)filter { + + if (!_compatibility || ![_compatibility length]) return YES; + + unichar *needle; + unichar *haystack; + NSUInteger needle_length; + NSUInteger haystack_length; + BOOL ok = NO; + + haystack_length = [_compatibility length]; + if (!haystack_length) return YES; + needle_length = [filter length]; + if (!needle_length) return NO; + + if (needle_length > haystack_length) return NO; + + haystack = malloc((haystack_length + 1) * sizeof(unichar)); + [_compatibility getCharacters: haystack range: NSMakeRange(0, haystack_length)]; + + needle = malloc((needle_length + 1) * sizeof(unichar)); + [filter getCharacters: needle range: NSMakeRange(0, needle_length)]; + + haystack[haystack_length] = 0; + needle[needle_length] = 0; + + NSUInteger i = 0; + unichar c; + do { + if (!memcmp(needle, haystack + i, sizeof(unichar) * needle_length)) { + i += needle_length; + c = haystack[i]; + if (c == ',' || c == 0) { ok = YES; break; } + } + do { + c = haystack[i++]; + } while ( c && c != ','); + } while (c); + + free(needle); + free(haystack); + + return ok; +} + +- (nonnull NSAttributedString *)menuAttributedTitle { + return nil; +} + +- (BOOL)menuEnabled { + return YES; +} + +- (BOOL)menuIsHeader { + return NO; +} + +- (nonnull NSString *)menuTitle { + return _title; +} + +-(NSString *)fullName { + if (![_list length]) return _name; + return [NSString stringWithFormat: @"%@:%@", _list, _name]; +} + @end @implementation SoftwareList + +-(SoftwareList *)filter: (NSString *)filter { + + unichar *needle = NULL; + __block unichar *haystack = NULL; + NSUInteger needle_length = 0; + __block NSUInteger max_haystack_length = 0; + + + needle_length = [filter length]; + if (!needle_length) return self; + + needle = malloc(needle_length * sizeof(unichar) + sizeof(unichar)); + [filter getCharacters: needle range: NSMakeRange(0, needle_length)]; + needle[needle_length] = 0; + + max_haystack_length = 127; + haystack = malloc(max_haystack_length * sizeof(unichar) + sizeof(unichar)); + + + NSPredicate *p = [NSPredicate predicateWithBlock: ^BOOL(Software *o, NSDictionary *bindings){ + + NSString *s = [o compatibility]; + NSUInteger length = [s length]; + if (length == 0) return YES; + if (length < needle_length) return NO; + + if (length > max_haystack_length) { + max_haystack_length = length; + haystack = realloc(haystack, sizeof(unichar ) * length + sizeof(unichar)); + } + + [s getCharacters: haystack range: NSMakeRange(0, length)]; + haystack[length] = 0; + + + NSUInteger i = 0; + unichar c; + do { + if (!memcmp(needle, haystack + i, sizeof(unichar) * needle_length)) { + i += needle_length; + c = haystack[i]; + if (c == ',' || c == 0) return YES; + } + do { + c = haystack[i++]; + } while ( c && c != ','); + } while (c); + return NO; + }]; + + NSArray *items = [_items filteredArrayUsingPredicate: p]; + free(needle); + free(haystack); + + if ([items count] == [_items count]) return self; + + SoftwareList *rv = [SoftwareList new]; + [rv setItems: items]; + [rv setName: _name]; + [rv setTitle: _title]; + + return rv; + +} + +- (nonnull NSAttributedString *)menuAttributedTitle { + return nil; +} + +- (BOOL)menuEnabled { + return NO; +} + +- (BOOL)menuIsHeader { + return YES; +} + +- (nonnull NSString *)menuTitle { + return _title; +} + @end + @interface SoftwareListDelegate : NSObject { unsigned _state; NSString *_name; NSString *_description; + NSString *_compatibility; NSMutableArray *_array; SoftwareList *_list; } @@ -82,6 +234,11 @@ } return; } + if ([@"sharedfeat" isEqualToString: elementName]) { + if ([@"compatibility" isEqualToString: [attributeDict objectForKey: @"name"]]) { + _compatibility = [attributeDict objectForKey: @"value"]; + } + } } -(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { @@ -110,10 +267,13 @@ Software *s = [Software new]; [s setTitle: _description]; [s setName: _name]; + [s setCompatibility: _compatibility]; + [s setList: [_list name]]; [_array addObject: s]; } _name = nil; _description = nil; + _compatibility = nil; } return; } @@ -137,7 +297,7 @@ static SoftwareList *LoadSoftwareList(NSURL *url, NSError **error) { - + NSXMLParser *p = [[NSXMLParser alloc] initWithContentsOfURL: url]; SoftwareListDelegate *d = [SoftwareListDelegate new]; @@ -160,6 +320,7 @@ NSArray *SoftwareListForMachine(NSString *machine) { if (!cache) cache = [NSCache new]; + if (!machine) return nil; machine = InternString(machine); NSArray *a = [cache objectForKey: machine]; if (a) return a; @@ -172,19 +333,46 @@ NSArray *SoftwareListForMachine(NSString *machine) { NSArray *list = [d objectForKey: @"software"]; NSMutableArray *tmp = [NSMutableArray new]; - for (NSString *xml in list) { + for (NSObject *o in list) { SoftwareList *sw; NSURL *url = SupportDirectory(); - url = [url URLByAppendingPathComponent: @"hash"]; - url = [url URLByAppendingPathComponent: xml]; - - NSError *error = nil; - sw = LoadSoftwareList(url, &error); - if (error) { - NSLog(@"SoftwareListForMachine: %@ %@: %@", machine, xml, error); + NSString *xml = nil; + NSString *filter = nil; + if ([o isKindOfClass: [NSString class]]) { + xml = (NSString *)o; + } else if ([o isKindOfClass: [NSDictionary class]]) { + xml = [(NSDictionary *)o objectForKey: @"name"]; + filter = [(NSDictionary *)o objectForKey: @"filter"]; + } else if ([o isKindOfClass: [NSArray class]]) { + // [ xml, filter ] + xml = [(NSArray *)o objectAtIndex: 0]; + filter = [(NSArray *)o objectAtIndex: 1]; + } + else { continue; } + if (!xml) continue; + + xml = InternString(xml); + sw = [cache objectForKey: xml]; + if (!sw) { + url = [url URLByAppendingPathComponent: @"hash"]; + url = [url URLByAppendingPathComponent: xml]; + + + NSError *error = nil; + sw = LoadSoftwareList(url, &error); + if (error) { + NSLog(@"SoftwareListForMachine: %@ %@: %@", machine, xml, error); + continue; + } + [cache setObject: sw forKey: xml]; + } + if (filter) { + sw = [sw filter: filter]; + } + if (!sw) continue; [tmp addObject: sw]; } @@ -202,3 +390,215 @@ NSArray *SoftwareListForMachine(NSString *machine) { return tmp; } + + +@interface SoftwareSet () { + + NSArray *_items; + NSCountedSet *_set; + NSCache *_cache; +} + +@end + +@implementation SoftwareSet + +-(NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id _Nullable [])buffer count:(NSUInteger)len { + return [_items countByEnumeratingWithState: state objects: buffer count: len]; +} + +-(void)buildSet { + if (_set) return; + _set = [NSCountedSet new]; + + for (SoftwareList *list in _items) { + for (Software *s in [list items]) { + [_set addObject: [s name]]; + } + } +} + +-(BOOL)nameIsUnique:(NSString *)name { + if (![name length]) return YES; + + if (!_set) [self buildSet]; + return [_set countForObject: name] <= 1; +} + ++(instancetype)softwareSetForMachine:(NSString *)machine { + + static NSCache *cache; + + if (!cache) + cache = [NSCache new]; + + if (!machine) return nil; + machine = InternString(machine); + SoftwareSet *s= [cache objectForKey: machine]; + if (s) return s; + + NSBundle *bundle = [NSBundle mainBundle]; + NSURL *url= [bundle URLForResource: machine withExtension: @"plist"]; + + NSDictionary *d = [NSDictionary dictionaryWithContentsOfURL: url]; + if (!d) return nil; + NSArray *list = [d objectForKey: @"software"]; + + NSMutableArray *tmp = [NSMutableArray new]; + for (NSObject *o in list) { + SoftwareList *sw; + NSURL *url = SupportDirectory(); + + NSString *xml = nil; + NSString *filter = nil; + if ([o isKindOfClass: [NSString class]]) { + xml = (NSString *)o; + } else if ([o isKindOfClass: [NSDictionary class]]) { + xml = [(NSDictionary *)o objectForKey: @"name"]; + filter = [(NSDictionary *)o objectForKey: @"filter"]; + } else if ([o isKindOfClass: [NSArray class]]) { + // [ xml, filter ] + xml = [(NSArray *)o objectAtIndex: 0]; + filter = [(NSArray *)o objectAtIndex: 1]; + } + else { + continue; + } + if (!xml) continue; + + xml = InternString(xml); + sw = [cache objectForKey: xml]; + if (!sw) { + url = [url URLByAppendingPathComponent: @"hash"]; + url = [url URLByAppendingPathComponent: xml]; + + + NSError *error = nil; + sw = LoadSoftwareList(url, &error); + if (error) { + NSLog(@"SoftwareListForMachine: %@ %@: %@", machine, xml, error); + continue; + } + [cache setObject: sw forKey: xml]; + } + if (filter) { + sw = [sw filter: filter]; + } + if (!sw) continue; + + [tmp addObject: sw]; + } + + if (![tmp count]) return nil; + + s = [SoftwareSet new]; + s->_items = tmp; + [cache setObject: s forKey: machine]; + return s; +} + +- (nonnull NSArray> *)autocomplete:(AutocompleteControl *)control completionsForItem:(id)item { + + for (SoftwareList *list in _items) { + NSArray *items = [list items]; + if ([items containsObject: item]) { + return @[ list, item ]; + } + } + return nil; +} + +- (nonnull NSArray> *)autocomplete:(nonnull AutocompleteControl *)control completionsForString:(nonnull NSString *)string { + + if (!_cache) { + _cache = [NSCache new]; + [_cache setCountLimit: 10]; + } + + + + enum { max_haystack_length = 256, max_needle_length = 256 }; + + unichar needle_data[max_needle_length]; + + if (!_items) return @[]; + + NSUInteger needle_length = [string length]; + needle_length = MIN(needle_length, max_needle_length); + + [string getCharacters: needle_data range: NSMakeRange(0, needle_length)]; + + for (NSUInteger i = 0; i < needle_length; ++i) + needle_data[i] = towlower(needle_data[i]); + + string = InternString([NSString stringWithCharacters: needle_data length: needle_length]); + + NSArray *a = [_cache objectForKey: string]; + if (a) return a; + + + NSMutableArray *rv = [NSMutableArray new]; + + if (needle_length == 0) { + for(SoftwareList *list in _items) { + [rv addObject: list]; + [rv addObjectsFromArray: [list items]]; + } + [_cache setObject: rv forKey: string]; + return rv; + } + + //if (needle_length < 2) return nil; + + + + const unichar *needle_data_ptr = needle_data; + NSPredicate *p = [NSPredicate predicateWithBlock: ^BOOL(Software *o, NSDictionary *bindings){ + // prefix match. + + unichar haystack_data[max_haystack_length]; + NSString *haystack; + NSUInteger length; + + haystack = [o name]; + + length = [haystack length]; + length = MIN(length, max_haystack_length); + if (length >= needle_length) { + [haystack getCharacters: haystack_data range: NSMakeRange(0, length)]; + for (NSUInteger i = 0; i < length; ++i) + haystack_data[i] = towlower(haystack_data[i]); + if (!memcmp(haystack_data, needle_data_ptr, needle_length * sizeof(unichar))) return YES; + } + + haystack = [o title]; + length = [haystack length]; + length = MIN(length, max_haystack_length); + if (length >= needle_length) { + [haystack getCharacters: haystack_data range: NSMakeRange(0, length)]; + for (NSUInteger i = 0; i < length; ++i) + haystack_data[i] = towlower(haystack_data[i]); + if (!memcmp(haystack_data, needle_data_ptr, needle_length * sizeof(unichar))) return YES; + } + + return NO; + }]; + + for (SoftwareList *list in _items) { + + NSArray *items = [list items]; + + NSArray *tmp = [items filteredArrayUsingPredicate: p]; + // add header ... ? + if (![tmp count]) continue; + [rv addObject: list]; // header + [rv addObjectsFromArray: tmp]; + + } + + [_cache setObject: rv forKey: string]; + return rv; + +} + +@end diff --git a/python/mkmachines.py b/python/mkmachines.py index 09a38d4..3de397f 100644 --- a/python/mkmachines.py +++ b/python/mkmachines.py @@ -232,9 +232,15 @@ def find_media(parent, include_slots=False): return media +def one_software(x): + xml = x.get("name") + ".xml" + filter = x.get("filter") + if filter: return { "name": xml, "filter": filter } + return xml + def find_software(parent): swl = parent.findall("./softwarelist") - return [x.get("name") + ".xml" for x in swl] + return [one_software(x) for x in swl]