X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=OSX%2FSaverListController.m;h=904f570a6af2afaf453cf4c960852b14aa4f85ff;hb=d5186197bc394e10a4402f7f6d23fbb14103bc50;hp=7eadc95ec327ad0eab80bd99812bcc7934b7b6f8;hpb=f8cf5ac7b2f53510f80a0eaf286a25298be17bfe;p=xscreensaver diff --git a/OSX/SaverListController.m b/OSX/SaverListController.m index 7eadc95e..904f570a 100644 --- a/OSX/SaverListController.m +++ b/OSX/SaverListController.m @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright (c) 2012 Jamie Zawinski +/* xscreensaver, Copyright (c) 2012-2014 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -15,6 +15,9 @@ #import "SaverListController.h" +#import "SaverRunner.h" +#import "yarandom.h" +#import "version.h" #undef countof #define countof(x) (sizeof((x))/sizeof((*x))) @@ -22,11 +25,123 @@ @implementation SaverListController -- (id)initWithNames:(NSArray *)names descriptions:(NSDictionary *)descs; +- (void) titleTapped:(id) sender +{ + [[UIApplication sharedApplication] + openURL:[NSURL URLWithString:@"http://www.jwz.org/xscreensaver/"]]; +} + + +- (void)makeTitleBar +{ + // Extract the version number and release date from the version string. + // Here's an area where I kind of wish I had "Two Problems". + // I guess I could add custom key to the Info.plist for this. + + NSArray *a = [[NSString stringWithCString: screensaver_id + encoding:NSASCIIStringEncoding] + componentsSeparatedByCharactersInSet: + [NSCharacterSet + characterSetWithCharactersInString:@" ()-"]]; + NSString *vers = [a objectAtIndex: 3]; + NSString *year = [a objectAtIndex: 7]; + + NSString *line1 = [@"XScreenSaver " stringByAppendingString: vers]; + NSString *line2 = [@"\u00A9 " stringByAppendingString: + [year stringByAppendingString: + @" Jamie Zawinski "]]; + + UIView *v = [[UIView alloc] initWithFrame:CGRectZero]; + + // The "go to web page" button on the right + + UIImage *img = [UIImage imageWithContentsOfFile: + [[[NSBundle mainBundle] bundlePath] + stringByAppendingPathComponent: + @"iSaverRunner57t.png"]]; + UIButton *button = [[UIButton alloc] init]; + [button setFrame: CGRectMake(0, 0, img.size.width/2, img.size.height/2)]; + [button setBackgroundImage:img forState:UIControlStateNormal]; + [button addTarget:self + action:@selector(titleTapped:) + forControlEvents:UIControlEventTouchUpInside]; + self.navigationItem.rightBarButtonItem = + [[UIBarButtonItem alloc] initWithCustomView: button]; + [button release]; + + // The title bar + + UILabel *label1 = [[UILabel alloc] initWithFrame:CGRectZero]; + UILabel *label2 = [[UILabel alloc] initWithFrame:CGRectZero]; + [label1 setText: line1]; + [label2 setText: line2]; + [label1 setBackgroundColor:[UIColor clearColor]]; + [label2 setBackgroundColor:[UIColor clearColor]]; + + [label1 setFont: [UIFont boldSystemFontOfSize: 17]]; + [label2 setFont: [UIFont systemFontOfSize: 12]]; + [label1 sizeToFit]; + [label2 sizeToFit]; + + CGRect r1 = [label1 frame]; + CGRect r2 = [label2 frame]; + CGRect r3 = r2; + + CGRect win = [self view].frame; + if (win.size.width > 320) { // iPad + [label1 setTextAlignment: NSTextAlignmentLeft]; + [label2 setTextAlignment: NSTextAlignmentRight]; + label2.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; + r3.size.width = win.size.width; + r1 = r3; + r1.origin.x += 6; + r1.size.width -= 12; + r2 = r1; + + } else { // iPhone + r3.size.width = 320; // force it to be flush-left + [label1 setTextAlignment: NSTextAlignmentLeft]; + [label2 setTextAlignment: NSTextAlignmentLeft]; + r1.origin.y = -1; // make it fit in landscape + r2.origin.y = r1.origin.y + r1.size.height - 2; + r3.size.height = r1.size.height + r2.size.height; + } + v.autoresizingMask = UIViewAutoresizingFlexibleWidth; + [label1 setFrame:r1]; + [label2 setFrame:r2]; + [v setFrame:r3]; + + [v addSubview:label1]; + [v addSubview:label2]; + + // Default opacity looks bad. + [v setBackgroundColor:[[v backgroundColor] colorWithAlphaComponent:1]]; + + self.navigationItem.titleView = v; + + win.origin.x = 0; + win.origin.y = 0; + win.size.height = 44; // #### This cannot possibly be right. + UISearchBar *search = [[UISearchBar alloc] initWithFrame:win]; + search.delegate = self; + search.placeholder = @"Search..."; + self.tableView.tableHeaderView = search; + + // Dismiss the search field's keyboard as soon as we scroll. +# ifdef __IPHONE_7_0 + if ([self.tableView respondsToSelector:@selector(keyboardDismissMode)]) + [self.tableView setKeyboardDismissMode: + UIScrollViewKeyboardDismissModeOnDrag]; +# endif +} + + +- (id)initWithNames:(NSArray *)_names descriptions:(NSDictionary *)_descs; { self = [self init]; if (! self) return 0; - [self reload:names descriptions:descs]; + [self reload:_names descriptions:_descs search:nil]; + [self makeTitleBar]; return self; } @@ -36,6 +151,8 @@ int n = countof(list_by_letter); NSMutableArray *a = [NSMutableArray arrayWithCapacity: n]; for (int i = 0; i < n; i++) { + if ([list_by_letter[i] count] == 0) // Omit empty letter sections. + continue; char s[2]; s[0] = (i == 'Z'-'A'+1 ? '#' : i+'A'); s[1] = 0; @@ -46,11 +163,25 @@ } -- (void) reload:(NSArray *)names descriptions:(NSDictionary *)descs +/* Called when text is typed into the top search bar. + */ +- (void)searchBar:(UISearchBar *)bar textDidChange:(NSString *)txt { - if (descriptions) - [descriptions release]; - descriptions = [descs retain]; + [self reload:names descriptions:descriptions search:txt]; +} + + +- (void) reload:(NSArray *)_names descriptions:(NSDictionary *)_descs + search:search +{ + if (names != _names) { + if (names) [names release]; + names = [_names retain]; + } + if (_descs != descriptions) { + if (descriptions) [descriptions release]; + descriptions = [_descs retain]; + } int n = countof(list_by_letter); for (int i = 0; i < n; i++) { @@ -58,6 +189,25 @@ } for (NSString *name in names) { + + // If we're searching, omit any items that don't have a match in the + // title or description. + // + BOOL matchp = (!search || [search length] == 0); + if (! matchp) { + matchp = ([name rangeOfString:search + options:NSCaseInsensitiveSearch].location + != NSNotFound); + } + if (! matchp) { + NSString *desc = [descriptions objectForKey:name]; + matchp = ([desc rangeOfString:search + options:NSCaseInsensitiveSearch].location + != NSNotFound); + } + if (! matchp) + continue; + int index = ([name cStringUsingEncoding: NSASCIIStringEncoding])[0]; if (index >= 'a' && index <= 'z') index -= 'a'-'A'; @@ -120,81 +270,60 @@ - (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)ip { - NSString *id = + NSString *title = [[letter_sections objectAtIndex: [ip indexAtPosition: 0]] objectAtIndex: [ip indexAtPosition: 1]]; - NSString *desc = [descriptions objectForKey:id]; + NSString *desc = [descriptions objectForKey:title]; - UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier: id]; - if (!cell) { + NSString *id = @"Cell"; + UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:id]; + if (!cell) cell = [[[UITableViewCell alloc] - initWithStyle: (desc - ? UITableViewCellStyleSubtitle - : UITableViewCellStyleDefault) + initWithStyle: UITableViewCellStyleSubtitle reuseIdentifier: id] autorelease]; - cell.textLabel.text = id; - if (desc) - cell.detailTextLabel.text = desc; - } + + cell.textLabel.text = title; + cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton; + cell.detailTextLabel.text = desc; + return cell; } -- (void)tapTimer:(NSTimer *)t +/* Selecting a row launches the saver. + */ +- (void)tableView:(UITableView *)tv + didSelectRowAtIndexPath:(NSIndexPath *)ip { - [last_tap release]; - last_tap = 0; - tap_count = 0; - tap_timer = 0; -} + UITableViewCell *cell = [tv cellForRowAtIndexPath: ip]; + SaverRunner *s = + (SaverRunner *) [[UIApplication sharedApplication] delegate]; + if (! s) return; + + // Dismiss the search field's keyboard before launching a saver. + [self.tableView.tableHeaderView resignFirstResponder]; + NSAssert ([s isKindOfClass:[SaverRunner class]], @"not a SaverRunner"); + [s loadSaver: cell.textLabel.text]; +} +/* Selecting a row's Disclosure Button opens the preferences. + */ - (void)tableView:(UITableView *)tv - didSelectRowAtIndexPath:(NSIndexPath *)ip + accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)ip { UITableViewCell *cell = [tv cellForRowAtIndexPath: ip]; - selected = cell.textLabel.text; - [self.navigationItem.leftBarButtonItem setEnabled: !!selected]; - [self.navigationItem.rightBarButtonItem setEnabled: !!selected]; - - if (tap_count == 0) { // First tap - tap_count = 1; - last_tap = [[ip copy] retain]; - tap_timer = [NSTimer scheduledTimerWithTimeInterval: 0.3 - target:self - selector:@selector(tapTimer:) - userInfo:nil - repeats:NO]; - - } else if (tap_count == 1 && tap_timer && // Second tap - [ip isEqual:last_tap]) { - [tap_timer invalidate]; - [last_tap release]; - last_tap = 0; - tap_timer = 0; - tap_count = 0; - - // Press the leftmost button in the button-bar. - UIBarButtonItem *b = self.navigationItem.leftBarButtonItem; - [[b target] performSelector: [b action] withObject: cell]; - - } else if (! [ip isEqual:last_tap]) { // Tap on a new row - if (tap_timer) [tap_timer invalidate]; - tap_timer = 0; - tap_count = 0; - } + SaverRunner *s = + (SaverRunner *) [[UIApplication sharedApplication] delegate]; + if (! s) return; + NSAssert ([s isKindOfClass:[SaverRunner class]], @"not a SaverRunner"); + [s openPreferences: cell.textLabel.text]; } -/* We can't select a row immediately after creation, but selecting it - a little while later works (presumably after redisplay has happened) - so do it on a timer. - */ -- (void) scrollToCB: (NSTimer *) timer +- (void) scrollTo: (NSString *) name { - NSString *name = [timer userInfo]; - int i = 0; int j = 0; Bool ok = NO; @@ -213,46 +342,46 @@ [self.tableView selectRowAtIndexPath:ip animated:NO scrollPosition: UITableViewScrollPositionMiddle]; - [self tableView:self.tableView didSelectRowAtIndexPath:ip]; } } -- (void) scrollTo: (NSString *) name +/* We need this to respond to "shake" gestures + */ +- (BOOL)canBecomeFirstResponder { - [NSTimer scheduledTimerWithTimeInterval: 0 - target:self - selector:@selector(scrollToCB:) - userInfo:name - repeats:NO]; + return YES; } - -- (void)viewWillAppear:(BOOL)animated +- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event { - /* Hitting the back button and returing to this view deselects, - and we can't re-select it from here, so again, do it once - we return to the event loop. - */ - if (selected) - [NSTimer scheduledTimerWithTimeInterval: 0 - target:self - selector:@selector(scrollToCB:) - userInfo:selected - repeats:NO]; - [super viewWillAppear:animated]; } - -- (NSString *) selected +- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event { - return selected; } -- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)o +/* Shake means load a random screen saver. + */ +- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event { - return YES; + if (motion != UIEventSubtypeMotionShake) + return; + NSMutableArray *a = [NSMutableArray arrayWithCapacity: 200]; + for (NSArray *sec in letter_sections) + for (NSString *s in sec) + [a addObject: s]; + int n = [a count]; + if (! n) return; + NSString *which = [a objectAtIndex: (random() % n)]; + + SaverRunner *s = + (SaverRunner *) [[UIApplication sharedApplication] delegate]; + if (! s) return; + NSAssert ([s isKindOfClass:[SaverRunner class]], @"not a SaverRunner"); + [self scrollTo: which]; + [s loadSaver: which]; }