1 /* xscreensaver, Copyright (c) 2012-2014 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
11 * This implements the top-level screen-saver selection list in the iOS app.
14 #ifdef USE_IPHONE // whole file
17 #import "SaverListController.h"
18 #import "SaverRunner.h"
23 #define countof(x) (sizeof((x))/sizeof((*x)))
26 @implementation SaverListController
28 - (void) titleTapped:(id) sender
30 [[UIApplication sharedApplication]
31 openURL:[NSURL URLWithString:@"https://www.jwz.org/xscreensaver/"]];
37 // Extract the version number and release date from the version string.
38 // Here's an area where I kind of wish I had "Two Problems".
39 // I guess I could add custom key to the Info.plist for this.
41 NSArray *a = [[NSString stringWithCString: screensaver_id
42 encoding:NSASCIIStringEncoding]
43 componentsSeparatedByCharactersInSet:
45 characterSetWithCharactersInString:@" ()-"]];
46 NSString *vers = [a objectAtIndex: 3];
47 NSString *year = [a objectAtIndex: 7];
49 NSString *line1 = [@"XScreenSaver " stringByAppendingString: vers];
50 NSString *line2 = [@"\u00A9 " stringByAppendingString:
51 [year stringByAppendingString:
52 @" Jamie Zawinski <jwz@jwz.org>"]];
54 UIView *v = [[UIView alloc] initWithFrame:CGRectZero];
56 // The "go to web page" button on the right
58 UIImage *img = [UIImage imageWithContentsOfFile:
59 [[[NSBundle mainBundle] bundlePath]
60 stringByAppendingPathComponent:
61 @"iSaverRunner57t.png"]];
62 UIButton *button = [[UIButton alloc] init];
63 [button setFrame: CGRectMake(0, 0, img.size.width/2, img.size.height/2)];
64 [button setBackgroundImage:img forState:UIControlStateNormal];
65 [button addTarget:self
66 action:@selector(titleTapped:)
67 forControlEvents:UIControlEventTouchUpInside];
68 UIBarButtonItem *bi = [[UIBarButtonItem alloc] initWithCustomView: button];
69 self.navigationItem.rightBarButtonItem = bi;
75 UILabel *label1 = [[UILabel alloc] initWithFrame:CGRectZero];
76 UILabel *label2 = [[UILabel alloc] initWithFrame:CGRectZero];
77 [label1 setText: line1];
78 [label2 setText: line2];
79 [label1 setBackgroundColor:[UIColor clearColor]];
80 [label2 setBackgroundColor:[UIColor clearColor]];
82 [label1 setFont: [UIFont boldSystemFontOfSize: 17]];
83 [label2 setFont: [UIFont systemFontOfSize: 12]];
87 CGRect r1 = [label1 frame];
88 CGRect r2 = [label2 frame];
91 CGRect win = [self view].frame;
92 if (win.size.width > 414 && win.size.height > 414) { // iPad
93 [label1 setTextAlignment: NSTextAlignmentLeft];
94 [label2 setTextAlignment: NSTextAlignmentRight];
95 label2.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
96 r3.size.width = win.size.width;
103 r3.size.width = win.size.width; // force it to be flush-left
104 [label1 setTextAlignment: NSTextAlignmentLeft];
105 [label2 setTextAlignment: NSTextAlignmentLeft];
106 r1.origin.y = -1; // make it fit in landscape
107 r2.origin.y = r1.origin.y + r1.size.height - 2;
108 r3.size.height = r1.size.height + r2.size.height;
110 v.autoresizingMask = UIViewAutoresizingFlexibleWidth;
111 [label1 setFrame:r1];
112 [label2 setFrame:r2];
115 [v addSubview:label1];
116 [v addSubview:label2];
118 // Default opacity looks bad.
119 [v setBackgroundColor:[[v backgroundColor] colorWithAlphaComponent:1]];
121 self.navigationItem.titleView = v;
125 win.size.height = 44; // #### This cannot possibly be right.
126 UISearchBar *search = [[UISearchBar alloc] initWithFrame:win];
127 search.delegate = self;
128 search.placeholder = @"Search...";
129 self.tableView.tableHeaderView = search;
131 // Dismiss the search field's keyboard as soon as we scroll.
133 if ([self.tableView respondsToSelector:@selector(keyboardDismissMode)])
134 [self.tableView setKeyboardDismissMode:
135 UIScrollViewKeyboardDismissModeOnDrag];
140 - (id)initWithNames:(NSArray *)_names descriptions:(NSDictionary *)_descs;
143 if (! self) return 0;
144 [self reload:_names descriptions:_descs search:nil];
150 - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tv
152 int n = countof(list_by_letter);
153 NSMutableArray *a = [NSMutableArray arrayWithCapacity: n];
154 for (int i = 0; i < n; i++) {
155 if ([list_by_letter[i] count] == 0) // Omit empty letter sections.
158 s[0] = (i == 'Z'-'A'+1 ? '#' : i+'A');
160 [a addObject: [NSString stringWithCString:s
161 encoding:NSASCIIStringEncoding]];
167 /* Called when text is typed into the top search bar.
169 - (void)searchBar:(UISearchBar *)bar textDidChange:(NSString *)txt
171 [self reload:names descriptions:descriptions search:txt];
175 - (void) reload:(NSArray *)_names descriptions:(NSDictionary *)_descs
178 if (names != _names) {
179 if (names) [names release];
180 names = [_names retain];
182 if (_descs != descriptions) {
183 if (descriptions) [descriptions release];
184 descriptions = [_descs retain];
187 int n = countof(list_by_letter);
188 for (int i = 0; i < n; i++) {
189 list_by_letter[i] = [[NSMutableArray alloc] init];
192 for (NSString *name in names) {
194 // If we're searching, omit any items that don't have a match in the
195 // title or description.
197 BOOL matchp = (!search || [search length] == 0);
199 matchp = ([name rangeOfString:search
200 options:NSCaseInsensitiveSearch].location
204 NSString *desc = [descriptions objectForKey:name];
205 matchp = ([desc rangeOfString:search
206 options:NSCaseInsensitiveSearch].location
212 int index = ([name cStringUsingEncoding: NSASCIIStringEncoding])[0];
213 if (index >= 'a' && index <= 'z')
215 if (index >= 'A' && index <= 'Z')
219 [list_by_letter[index] addObject: name];
222 active_section_count = 0;
223 letter_sections = [[[NSMutableArray alloc] init] retain];
224 section_titles = [[[NSMutableArray alloc] init] retain];
225 for (int i = 0; i < n; i++) {
226 if ([list_by_letter[i] count] > 0) {
227 active_section_count++;
228 [letter_sections addObject: list_by_letter[i]];
230 [section_titles addObject: [NSString stringWithFormat: @"%c", i+'A']];
232 [section_titles addObject: @"#"];
235 [self.tableView reloadData];
239 - (NSString *)tableView:(UITableView *)tv
240 titleForHeaderInSection:(NSInteger)section
242 return [section_titles objectAtIndex: section];
245 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tv
247 return active_section_count;
251 - (NSInteger)tableView:(UITableView *)tv
252 numberOfRowsInSection:(NSInteger)section
254 return [[letter_sections objectAtIndex: section] count];
257 - (NSInteger)tableView:(UITableView *)tv
258 sectionForSectionIndexTitle:(NSString *)title
259 atIndex:(NSInteger) index
262 for (NSString *sectionTitle in section_titles) {
263 if ([sectionTitle isEqualToString: title])
271 - (UITableViewCell *)tableView:(UITableView *)tv
272 cellForRowAtIndexPath:(NSIndexPath *)ip
275 [[letter_sections objectAtIndex: [ip indexAtPosition: 0]]
276 objectAtIndex: [ip indexAtPosition: 1]];
277 NSString *desc = [descriptions objectForKey:title];
279 NSString *id = @"Cell";
280 UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:id];
282 cell = [[[UITableViewCell alloc]
283 initWithStyle: UITableViewCellStyleSubtitle
287 cell.textLabel.text = title;
288 cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
289 cell.detailTextLabel.text = desc;
295 /* Selecting a row launches the saver.
297 - (void)tableView:(UITableView *)tv
298 didSelectRowAtIndexPath:(NSIndexPath *)ip
300 UITableViewCell *cell = [tv cellForRowAtIndexPath: ip];
302 (SaverRunner *) [[UIApplication sharedApplication] delegate];
305 // Dismiss the search field's keyboard before launching a saver.
306 [self.tableView.tableHeaderView resignFirstResponder];
308 NSAssert ([s isKindOfClass:[SaverRunner class]], @"not a SaverRunner");
309 [s loadSaver: cell.textLabel.text];
312 /* Selecting a row's Disclosure Button opens the preferences.
314 - (void)tableView:(UITableView *)tv
315 accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)ip
317 UITableViewCell *cell = [tv cellForRowAtIndexPath: ip];
319 (SaverRunner *) [[UIApplication sharedApplication] delegate];
321 NSAssert ([s isKindOfClass:[SaverRunner class]], @"not a SaverRunner");
322 [s openPreferences: cell.textLabel.text];
326 - (void) scrollTo: (NSString *) name
331 for (NSArray *a in letter_sections) {
333 for (NSString *n in a) {
334 ok = [n isEqualToString: name];
342 NSIndexPath *ip = [NSIndexPath indexPathForRow: j inSection: i];
343 [self.tableView selectRowAtIndexPath:ip
345 scrollPosition: UITableViewScrollPositionMiddle];
350 /* We need this to respond to "shake" gestures
352 - (BOOL)canBecomeFirstResponder
357 - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
361 - (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event
366 /* Shake means load a random screen saver.
368 - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
370 if (motion != UIEventSubtypeMotionShake)
372 NSMutableArray *a = [NSMutableArray arrayWithCapacity: 200];
373 for (NSArray *sec in letter_sections)
374 for (NSString *s in sec)
378 NSString *which = [a objectAtIndex: (random() % n)];
381 (SaverRunner *) [[UIApplication sharedApplication] delegate];
383 NSAssert ([s isKindOfClass:[SaverRunner class]], @"not a SaverRunner");
384 [self scrollTo: which];
385 [s loadSaver: which];
391 for (int i = 0; i < countof(list_by_letter); i++)
392 [list_by_letter[i] release];
393 [letter_sections release];
394 [section_titles release];
395 [descriptions release];
402 #endif // USE_IPHONE -- whole file