From d3a98cf6cbc3bd0b9efc570f58e8812c03931c18 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 16 Oct 2018 10:08:48 +0200 Subject: Original 5.40 --- OSX/SaverListController.m | 402 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 402 insertions(+) create mode 100644 OSX/SaverListController.m (limited to 'OSX/SaverListController.m') diff --git a/OSX/SaverListController.m b/OSX/SaverListController.m new file mode 100644 index 0000000..9377275 --- /dev/null +++ b/OSX/SaverListController.m @@ -0,0 +1,402 @@ +/* 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 + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + * + * This implements the top-level screen-saver selection list in the iOS app. + */ + +#ifdef USE_IPHONE // whole file + + +#import "SaverListController.h" +#import "SaverRunner.h" +#import "yarandom.h" +#import "version.h" + +#undef countof +#define countof(x) (sizeof((x))/sizeof((*x))) + + +@implementation SaverListController + +- (void) titleTapped:(id) sender +{ + [[UIApplication sharedApplication] + openURL:[NSURL URLWithString:@"https://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]; + UIBarButtonItem *bi = [[UIBarButtonItem alloc] initWithCustomView: button]; + self.navigationItem.rightBarButtonItem = bi; + [bi release]; + [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 > 414 && win.size.height > 414) { // 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 = win.size.width; // 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 search:nil]; + [self makeTitleBar]; + return self; +} + + +- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tv +{ + 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; + [a addObject: [NSString stringWithCString:s + encoding:NSASCIIStringEncoding]]; + } + return a; +} + + +/* Called when text is typed into the top search bar. + */ +- (void)searchBar:(UISearchBar *)bar textDidChange:(NSString *)txt +{ + [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++) { + list_by_letter[i] = [[NSMutableArray alloc] init]; + } + + 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'; + if (index >= 'A' && index <= 'Z') + index -= 'A'; + else + index = n-1; + [list_by_letter[index] addObject: name]; + } + + active_section_count = 0; + letter_sections = [[[NSMutableArray alloc] init] retain]; + section_titles = [[[NSMutableArray alloc] init] retain]; + for (int i = 0; i < n; i++) { + if ([list_by_letter[i] count] > 0) { + active_section_count++; + [letter_sections addObject: list_by_letter[i]]; + if (i <= 'Z'-'A') + [section_titles addObject: [NSString stringWithFormat: @"%c", i+'A']]; + else + [section_titles addObject: @"#"]; + } + } + [self.tableView reloadData]; +} + + +- (NSString *)tableView:(UITableView *)tv + titleForHeaderInSection:(NSInteger)section +{ + return [section_titles objectAtIndex: section]; +} + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tv +{ + return active_section_count; +} + + +- (NSInteger)tableView:(UITableView *)tv + numberOfRowsInSection:(NSInteger)section +{ + return [[letter_sections objectAtIndex: section] count]; +} + +- (NSInteger)tableView:(UITableView *)tv + sectionForSectionIndexTitle:(NSString *)title + atIndex:(NSInteger) index +{ + int i = 0; + for (NSString *sectionTitle in section_titles) { + if ([sectionTitle isEqualToString: title]) + return i; + i++; + } + return -1; +} + + +- (UITableViewCell *)tableView:(UITableView *)tv + cellForRowAtIndexPath:(NSIndexPath *)ip +{ + NSString *title = + [[letter_sections objectAtIndex: [ip indexAtPosition: 0]] + objectAtIndex: [ip indexAtPosition: 1]]; + NSString *desc = [descriptions objectForKey:title]; + + NSString *id = @"Cell"; + UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:id]; + if (!cell) + cell = [[[UITableViewCell alloc] + initWithStyle: UITableViewCellStyleSubtitle + reuseIdentifier: id] + autorelease]; + + cell.textLabel.text = title; + cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton; + cell.detailTextLabel.text = desc; + + return cell; +} + + +/* Selecting a row launches the saver. + */ +- (void)tableView:(UITableView *)tv + didSelectRowAtIndexPath:(NSIndexPath *)ip +{ + 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 + accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)ip +{ + UITableViewCell *cell = [tv cellForRowAtIndexPath: ip]; + SaverRunner *s = + (SaverRunner *) [[UIApplication sharedApplication] delegate]; + if (! s) return; + NSAssert ([s isKindOfClass:[SaverRunner class]], @"not a SaverRunner"); + [s openPreferences: cell.textLabel.text]; +} + + +- (void) scrollTo: (NSString *) name +{ + int i = 0; + int j = 0; + Bool ok = NO; + for (NSArray *a in letter_sections) { + j = 0; + for (NSString *n in a) { + ok = [n isEqualToString: name]; + if (ok) goto DONE; + j++; + } + i++; + } + DONE: + if (ok) { + NSIndexPath *ip = [NSIndexPath indexPathForRow: j inSection: i]; + [self.tableView selectRowAtIndexPath:ip + animated:NO + scrollPosition: UITableViewScrollPositionMiddle]; + } +} + + +/* We need this to respond to "shake" gestures + */ +- (BOOL)canBecomeFirstResponder +{ + return YES; +} + +- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event +{ +} + +- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event +{ +} + + +/* Shake means load a random screen saver. + */ +- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event +{ + 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]; +} + + +- (void)dealloc +{ + for (int i = 0; i < countof(list_by_letter); i++) + [list_by_letter[i] release]; + [letter_sections release]; + [section_titles release]; + [descriptions release]; + [super dealloc]; +} + +@end + + +#endif // USE_IPHONE -- whole file -- cgit v1.2.3-55-g7522