-/* xscreensaver, Copyright (c) 2012 Jamie Zawinski <jwz@jwz.org>
+/* 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
#import "SaverListController.h"
#import "SaverRunner.h"
+#import "yarandom.h"
#import "version.h"
#undef countof
- (void) titleTapped:(id) sender
{
[[UIApplication sharedApplication]
- openURL:[NSURL URLWithString:@"http://www.jwz.org/xscreensaver/"]];
+ openURL:[NSURL URLWithString:@"https://www.jwz.org/xscreensaver/"]];
}
{
// 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]
UIImage *img = [UIImage imageWithContentsOfFile:
[[[NSBundle mainBundle] bundlePath]
stringByAppendingPathComponent:
- @"iSaverRunner29t.png"]];
- UIBarButtonItem *button = [[[UIBarButtonItem alloc]
- initWithImage: img
- style: UIBarButtonItemStylePlain
- target: self
- action: @selector(titleTapped:)]
- autorelease];
- button.width = img.size.width;
- self.navigationItem.rightBarButtonItem = button;
+ @"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
CGRect r3 = r2;
CGRect win = [self view].frame;
- if (win.size.width > 320) { // iPad
- [label1 setTextAlignment: UITextAlignmentLeft];
- [label2 setTextAlignment: UITextAlignmentRight];
+ 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;
r2 = r1;
} else { // iPhone
- r3.size.width = 320; // force it to be flush-left
- [label1 setTextAlignment: UITextAlignmentLeft];
- [label2 setTextAlignment: UITextAlignmentLeft];
+ 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 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;
+- (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;
}
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;
}
-- (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++) {
}
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';
- (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;
- cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
- if (desc)
- cell.detailTextLabel.text = desc;
- }
+ cell.textLabel.text = title;
+ cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
+ cell.detailTextLabel.text = desc;
+
return cell;
}
SaverRunner *s =
(SaverRunner *) [[UIApplication sharedApplication] delegate];
if (! s) return;
- if (! [s isKindOfClass:[SaverRunner class]])
- abort();
+
+ // 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];
}
SaverRunner *s =
(SaverRunner *) [[UIApplication sharedApplication] delegate];
if (! s) return;
- if (! [s isKindOfClass:[SaverRunner class]])
- abort();
+ NSAssert ([s isKindOfClass:[SaverRunner class]], @"not a SaverRunner");
[s openPreferences: cell.textLabel.text];
}
}
-- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)o
+/* 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
{