Squashed commit of the following:

commit 1dd1d6d3eee928d283dafbaf4432b053dff65ac2
Author: Kelvin Sherlock <ksherlock@gmail.com>
Date:   Thu May 27 13:30:38 2021 -0400

    0.232 rom update

commit cfb7f9a4d1647c41fab3328bada0f37bdb041983
Author: Kelvin Sherlock <ksherlock@gmail.com>
Date:   Wed May 26 20:31:16 2021 -0400

    fix resizing to prevent horizontal scrolling

commit 5c393ab5a95c9e53c4ef1e0b3f2b77db93e1c29b
Author: Kelvin Sherlock <ksherlock@gmail.com>
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 <ksherlock@gmail.com>
Date:   Tue May 25 23:17:10 2021 -0400

    show software short name unless it's ambiguous.

commit 6f3b3bef5e18e78619959613e28064eb6251ce27
Author: Kelvin Sherlock <ksherlock@gmail.com>
Date:   Tue May 25 23:16:47 2021 -0400

    cleanup some auto complete rough edges

commit 4988d8b266b8b11e519bcb5aab05ad2c3ffe5c23
Author: Kelvin Sherlock <ksherlock@gmail.com>
Date:   Tue May 25 23:16:24 2021 -0400

    fix

commit 6d4bf94fd6b4850564b705ea15e91079b00346c2
Author: Kelvin Sherlock <ksherlock@gmail.com>
Date:   Tue May 25 19:13:28 2021 -0400

    clean up autocomplete a little...

commit 3722271e51d93599903bccc721dc1de61f0aae72
Author: Kelvin Sherlock <ksherlock@gmail.com>
Date:   Tue May 25 19:13:07 2021 -0400

    move auto complete logic to SoftwareSet

commit 04cc83ecbe9ee4c9100305712c47a11359327734
Author: Kelvin Sherlock <ksherlock@gmail.com>
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 <ksherlock@gmail.com>
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 <ksherlock@gmail.com>
Date:   Mon May 24 18:45:42 2021 -0400

    add auto complete software list to the launch window.

commit 9dc72b702c9b36d490349f291c10b98b56178567
Author: Kelvin Sherlock <ksherlock@gmail.com>
Date:   Mon May 24 18:44:28 2021 -0400

    autocomplete control.

commit 40eee83f56d957c64e0f8d927486f13165b9e120
Author: Kelvin Sherlock <ksherlock@gmail.com>
Date:   Mon May 24 18:39:32 2021 -0400

    add filters to software list.

commit cebfbaa545441f061cffafe8ca93db2377240a06
Author: Kelvin Sherlock <ksherlock@gmail.com>
Date:   Sun May 23 11:59:00 2021 -0400

    add compatibility field for software list filtering.
This commit is contained in:
Kelvin Sherlock 2021-05-27 19:42:28 -04:00
parent e48e2a9223
commit 3d4d0c92c7
58 changed files with 1734 additions and 63 deletions

View File

@ -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 = "<group>"; };
B66236B824FDA698006CABD7 /* mame64 */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; name = mame64; path = embedded/mame64; sourceTree = "<group>"; };
B66236C024FDB7A6006CABD7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = Base; path = Base.lproj/Credits.rtf; sourceTree = "<group>"; };
B6665C12265A0E3E00254939 /* AutocompleteControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AutocompleteControl.h; sourceTree = "<group>"; };
B6665C13265A0E3E00254939 /* AutocompleteControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AutocompleteControl.m; sourceTree = "<group>"; };
B6665C1B265C639900254939 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/Autocomplete.xib; sourceTree = "<group>"; };
B66D0FE62611386B000902F1 /* SoftwareList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SoftwareList.m; sourceTree = "<group>"; };
B66D0FE926113AA8000902F1 /* SoftwareList.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SoftwareList.h; sourceTree = "<group>"; };
B67BD48424EE249D0073E334 /* apple1.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = apple1.plist; sourceTree = "<group>"; };
@ -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 = "<group>";
};
B6665C1A265C639900254939 /* Autocomplete.xib */ = {
isa = PBXVariantGroup;
children = (
B6665C1B265C639900254939 /* Base */,
);
name = Autocomplete.xib;
sourceTree = "<group>";
};
B6BA258324E99BEB005FB8FF /* MainMenu.xib */ = {
isa = PBXVariantGroup;
children = (

View File

@ -0,0 +1,42 @@
//
// AutocompleteControl.h
// Autocomplete
//
// Created by Kelvin Sherlock on 2/20/2021.
// Copyright © 2021 Kelvin Sherlock. All rights reserved.
//
#import <Cocoa/Cocoa.h>
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<id<AutocompleteItem>> *)autocomplete: (AutocompleteControl *)control completionsForString: (NSString *)string;
-(NSArray<id<AutocompleteItem>> *)autocomplete: (AutocompleteControl *)control completionsForItem: (id<AutocompleteItem>)item;
@end
@interface AutocompleteControl : NSSearchField
@property NSInteger minWidth;
@property NSInteger maxDisplayItems;
@property (nullable, weak) id<AutoCompleteDelegate> autocompleteDelegate;
-(void)invalidate;
@end
NS_ASSUME_NONNULL_END

822
Ample/AutocompleteControl.m Normal file
View File

@ -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 <wctype.h>
/*
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<id<AutocompleteItem>> *items;
@property (weak) AutocompleteControl *parent;
-(void)reset;
@end
@interface AutocompleteControl ()
{
IBOutlet NSPanel *_panel;
__weak IBOutlet ACMenuView *_menuView;
__weak IBOutlet NSScrollView *_scrollView;
id<AutocompleteItem> _value;
BOOL _editing;
BOOL _dirty;
}
@end
@interface AutocompleteControl (SearchField) <NSSearchFieldDelegate>
-(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<AutocompleteItem>)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<AutocompleteItem> _value;
NSInteger _index;
NSInteger _count;
NSArray<id<AutocompleteItem>> *_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<AutocompleteItem> 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<AutocompleteItem>)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<AutocompleteItem> 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<AutocompleteItem> 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<AutocompleteItem> 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<AutocompleteItem> 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<AutocompleteItem>)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<AutocompleteItem> 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

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="15705" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15705"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="AutocompleteControl">
<connections>
<outlet property="_menuView" destination="F2h-9b-Ouu" id="min-gE-sma"/>
<outlet property="_panel" destination="OYW-cj-FD5" id="DYJ-rN-6Ep"/>
<outlet property="_scrollView" destination="VQ6-PE-hTH" id="iKA-Ps-nkl"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" titlebarAppearsTransparent="YES" titleVisibility="hidden" id="OYW-cj-FD5" customClass="ACPanel">
<windowStyleMask key="styleMask" titled="YES" utility="YES" nonactivatingPanel="YES" fullSizeContentView="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="272" y="172" width="300" height="400"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<view key="contentView" id="3pz-HL-mV1">
<rect key="frame" x="0.0" y="0.0" width="300" height="400"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="0.0" horizontalPageScroll="0.0" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" id="VQ6-PE-hTH">
<rect key="frame" x="0.0" y="4" width="300" height="392"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="HWy-NN-aOV">
<rect key="frame" x="0.0" y="0.0" width="300" height="392"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view id="F2h-9b-Ouu" customClass="ACMenuView">
<rect key="frame" x="0.0" y="0.0" width="300" height="392"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
</view>
</subviews>
<color key="backgroundColor" name="windowBackgroundColor" catalog="System" colorSpace="catalog"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="Az6-1a-Hei">
<rect key="frame" x="-100" y="-100" width="168" height="15"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="tng-g2-1LZ" customClass="ACScroller">
<rect key="frame" x="285" y="0.0" width="15" height="392"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
</subviews>
</view>
<point key="canvasLocation" x="127" y="73"/>
</window>
</objects>
</document>

View File

@ -14,6 +14,7 @@
<outlet property="mediaView" destination="J9O-xI-P5J" id="PmZ-VC-4SN"/>
<outlet property="slotController" destination="lyS-mc-3Tf" id="LXo-Ii-fDX"/>
<outlet property="slotView" destination="P9d-sS-qEb" id="qlH-u0-hzq"/>
<outlet property="softwareListControl" destination="pNl-g4-ZLh" id="U0i-R9-GLE"/>
<outlet property="window" destination="Vze-YF-m6e" id="JUs-Eb-MW8"/>
</connections>
</customObject>
@ -372,7 +373,7 @@ DQ
</tabViewItems>
</tabView>
<customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="J9O-xI-P5J" customClass="FlippedView">
<rect key="frame" x="382" y="92" width="316" height="363"/>
<rect key="frame" x="382" y="92" width="316" height="331"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
</customView>
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" horizontalScrollElasticity="none" id="2ue-XT-byh">
@ -409,6 +410,18 @@ DQ
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
<searchField wantsLayer="YES" verticalHuggingPriority="750" textCompletion="NO" id="pNl-g4-ZLh" customClass="AutocompleteControl">
<rect key="frame" x="382" y="431" width="316" height="22"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" placeholderString="Software List" usesSingleLineMode="YES" bezelStyle="round" id="sMi-a3-XEn">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="systemRedColor" catalog="System" colorSpace="catalog"/>
</searchFieldCell>
<connections>
<action selector="softwareChanged:" target="-2" id="879-da-UEc"/>
</connections>
</searchField>
</subviews>
</view>
<point key="canvasLocation" x="769" y="120.5"/>

View File

@ -13,8 +13,11 @@
#import "MachineViewController.h"
#import "LogWindowController.h"
#import "AutocompleteControl.h"
#import "SoftwareList.h"
#include <sys/stat.h>
#include <wctype.h>
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

View File

@ -5944,7 +5944,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>apple2_cass.xml</string>
</array>

View File

@ -5884,7 +5884,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2P</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>apple2_cass.xml</string>
</array>

View File

@ -5884,7 +5884,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2P</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>apple2_cass.xml</string>
</array>

View File

@ -5884,7 +5884,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2P</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>apple2_cass.xml</string>
</array>

View File

@ -5884,7 +5884,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2P</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>apple2_cass.xml</string>
</array>

View File

@ -5944,7 +5944,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>apple2_cass.xml</string>
</array>

View File

@ -485,7 +485,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2C</string>
</dict>
<string>apple2_flop_misc.xml</string>
</array>
</dict>

View File

@ -447,7 +447,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2C</string>
</dict>
<string>apple2_flop_misc.xml</string>
</array>
</dict>

View File

@ -7672,7 +7672,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2E</string>
</dict>
<string>apple2_flop_misc.xml</string>
</array>
</dict>

View File

@ -7672,7 +7672,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2EE</string>
</dict>
<string>apple2_flop_misc.xml</string>
</array>
</dict>

View File

@ -7672,7 +7672,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2EE</string>
</dict>
<string>apple2_flop_misc.xml</string>
</array>
</dict>

View File

@ -7672,7 +7672,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2E</string>
</dict>
<string>apple2_flop_misc.xml</string>
</array>
</dict>

View File

@ -7672,7 +7672,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2EE</string>
</dict>
<string>apple2_flop_misc.xml</string>
</array>
</dict>

View File

@ -7672,7 +7672,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2E</string>
</dict>
<string>apple2_flop_misc.xml</string>
</array>
</dict>

View File

@ -7672,7 +7672,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2E</string>
</dict>
<string>apple2_flop_misc.xml</string>
</array>
</dict>

View File

@ -7481,7 +7481,12 @@
<array>
<string>apple2gs.xml</string>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2GS</string>
</dict>
<string>apple2_flop_misc.xml</string>
</array>
</dict>

View File

@ -7521,7 +7521,12 @@
<array>
<string>apple2gs.xml</string>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2GS</string>
</dict>
<string>apple2_flop_misc.xml</string>
</array>
</dict>

View File

@ -7521,7 +7521,12 @@
<array>
<string>apple2gs.xml</string>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2GS</string>
</dict>
<string>apple2_flop_misc.xml</string>
</array>
</dict>

View File

@ -5884,7 +5884,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2P</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>apple2_cass.xml</string>
</array>

View File

@ -5884,7 +5884,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2P</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>apple2_cass.xml</string>
</array>

View File

@ -5944,7 +5944,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>apple2_cass.xml</string>
</array>

View File

@ -6609,7 +6609,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2E</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>cecflop.xml</string>
</array>

View File

@ -6609,7 +6609,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2E</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>cecflop.xml</string>
</array>

View File

@ -6609,7 +6609,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2E</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>cecflop.xml</string>
</array>

View File

@ -6609,7 +6609,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2E</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>cecflop.xml</string>
</array>

View File

@ -6609,7 +6609,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2E</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>cecflop.xml</string>
</array>

View File

@ -5884,7 +5884,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2P</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>apple2_cass.xml</string>
</array>

View File

@ -5884,7 +5884,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2P</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>apple2_cass.xml</string>
</array>

View File

@ -5884,7 +5884,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2P</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>apple2_cass.xml</string>
</array>

View File

@ -5884,7 +5884,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2P</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>apple2_cass.xml</string>
</array>

View File

@ -5884,7 +5884,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2P</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>apple2_cass.xml</string>
</array>

View File

@ -445,7 +445,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2C</string>
</dict>
<string>apple2_flop_misc.xml</string>
</array>
</dict>

View File

@ -5157,7 +5157,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2C</string>
</dict>
<string>apple2_flop_misc.xml</string>
</array>
</dict>

View File

@ -5157,7 +5157,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2C</string>
</dict>
<string>apple2_flop_misc.xml</string>
</array>
</dict>

View File

@ -5884,7 +5884,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2P</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>apple2_cass.xml</string>
</array>

View File

@ -5884,7 +5884,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2P</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>apple2_cass.xml</string>
</array>

View File

@ -5884,7 +5884,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2P</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>apple2_cass.xml</string>
</array>

View File

@ -7672,7 +7672,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2E</string>
</dict>
<string>apple2_flop_misc.xml</string>
</array>
</dict>

View File

@ -5884,7 +5884,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2P</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>apple2_cass.xml</string>
</array>

View File

@ -7672,7 +7672,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2E</string>
</dict>
<string>apple2_flop_misc.xml</string>
</array>
</dict>

View File

@ -5884,7 +5884,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2P</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>apple2_cass.xml</string>
</array>

View File

@ -15,6 +15,7 @@
<string>a2ap16</string>
<string>a2ap16a</string>
<string>a2aplcrd</string>
<string>a2booti</string>
<string>a2bufgrapplerplus</string>
<string>a2bufgrapplerplusa</string>
<string>a2cffa02</string>
@ -154,6 +155,7 @@
<string>macsefd</string>
<string>maxxi</string>
<string>microeng</string>
<string>mockingboardd</string>
<string>mprof3</string>
<string>nb_48gc</string>
<string>nb_824gc</string>

View File

@ -5884,7 +5884,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2P</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>apple2_cass.xml</string>
</array>

View File

@ -7672,7 +7672,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2E</string>
</dict>
<string>apple2_flop_misc.xml</string>
</array>
</dict>

View File

@ -7672,7 +7672,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2E</string>
</dict>
<string>apple2_flop_misc.xml</string>
</array>
</dict>

View File

@ -5884,7 +5884,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2P</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>apple2_cass.xml</string>
</array>

View File

@ -5884,7 +5884,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2P</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>apple2_cass.xml</string>
</array>

View File

@ -5884,7 +5884,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2P</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>apple2_cass.xml</string>
</array>

View File

@ -6609,7 +6609,12 @@
<key>software</key>
<array>
<string>apple2_flop_clcracked.xml</string>
<string>apple2_flop_orig.xml</string>
<dict>
<key>name</key>
<string>apple2_flop_orig.xml</string>
<key>filter</key>
<string>A2E</string>
</dict>
<string>apple2_flop_misc.xml</string>
<string>cecflop.xml</string>
</array>

View File

@ -10,20 +10,37 @@
#define SoftwareList_h
#import <Foundation/Foundation.h>
#import "AutocompleteControl.h"
@interface SoftwareList : NSObject
@interface SoftwareList : NSObject <AutocompleteItem>
@property NSString *name;
@property NSString *title;
@property NSArray *items;
-(SoftwareList *)filter: (NSString *)filter;
@end
@interface Software : NSObject
@interface Software : NSObject <AutocompleteItem>
@property NSString *name;
@property NSString *title;
@property NSString *compatibility;
@property NSString *list;
-(NSString *)fullName;
@end
@interface SoftwareSet : NSObject <NSFastEnumeration, AutoCompleteDelegate>
+(instancetype)softwareSetForMachine: (NSString *)machine;
-(BOOL)nameIsUnique: (NSString *)name;
@end
NSArray<SoftwareList *> *SoftwareListForMachine(NSString *machine);
//NSArray<SoftwareList *> *SoftwareListForMachine(NSString *machine);
#endif /* SoftwareList_h */

View File

@ -7,22 +7,174 @@
//
#import <Foundation/Foundation.h>
#include "Ample.h"
#include <wctype.h>
#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<NSXMLParserDelegate> {
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<SoftwareList *> *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<SoftwareList *> *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<SoftwareList *> *SoftwareListForMachine(NSString *machine) {
return tmp;
}
@interface SoftwareSet () {
NSArray<SoftwareList *> *_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<id<AutocompleteItem>> *)autocomplete:(AutocompleteControl *)control completionsForItem:(id<AutocompleteItem>)item {
for (SoftwareList *list in _items) {
NSArray *items = [list items];
if ([items containsObject: item]) {
return @[ list, item ];
}
}
return nil;
}
- (nonnull NSArray<id<AutocompleteItem>> *)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

View File

@ -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]