blob: 9377275b7a51b01724d1e9e5f48d7f942dd16d27 (
plain) (
tree)
|
|
/* xscreensaver, Copyright (c) 2012-2014 Jamie Zawinski <jwz@jwz.org>
*
* 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 <jwz@jwz.org>"]];
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
|