From http://www.jwz.org/xscreensaver/xscreensaver-5.22.tar.gz
[xscreensaver] / OSX / SaverListController.m
1 /* xscreensaver, Copyright (c) 2012-2013 Jamie Zawinski <jwz@jwz.org>
2  *
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 
9  * implied warranty.
10  *
11  * This implements the top-level screen-saver selection list in the iOS app.
12  */
13
14 #ifdef USE_IPHONE  // whole file
15
16
17 #import "SaverListController.h"
18 #import "SaverRunner.h"
19 #import "yarandom.h"
20 #import "version.h"
21
22 #undef countof
23 #define countof(x) (sizeof((x))/sizeof((*x)))
24
25
26 @implementation SaverListController
27
28 - (void) titleTapped:(id) sender
29 {
30   [[UIApplication sharedApplication]
31     openURL:[NSURL URLWithString:@"http://www.jwz.org/xscreensaver/"]];
32 }
33
34
35 - (void)makeTitleBar
36 {
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.
40
41   NSArray *a = [[NSString stringWithCString: screensaver_id
42                           encoding:NSASCIIStringEncoding]
43                  componentsSeparatedByCharactersInSet:
44                    [NSCharacterSet
45                      characterSetWithCharactersInString:@" ()-"]];
46   NSString *vers = [a objectAtIndex: 3];
47   NSString *year = [a objectAtIndex: 7];
48
49   NSString *line1 = [@"XScreenSaver " stringByAppendingString: vers];
50   NSString *line2 = [@"\u00A9 " stringByAppendingString:
51                         [year stringByAppendingString:
52                                 @" Jamie Zawinski <jwz@jwz.org>"]];
53
54   UIView *v = [[UIView alloc] initWithFrame:CGRectZero];
55
56   // The "go to web page" button on the right
57
58   UIImage *img = [UIImage imageWithContentsOfFile:
59                             [[[NSBundle mainBundle] bundlePath]
60                               stringByAppendingPathComponent:
61                                 @"iSaverRunner29t.png"]];
62   UIBarButtonItem *button = [[[UIBarButtonItem alloc]
63                                initWithImage: img
64                                style: UIBarButtonItemStylePlain
65                                target: self
66                                action: @selector(titleTapped:)]
67                               autorelease];
68   button.width = img.size.width;
69   self.navigationItem.rightBarButtonItem = button;
70
71   // The title bar
72
73   UILabel *label1 = [[UILabel alloc] initWithFrame:CGRectZero];
74   UILabel *label2 = [[UILabel alloc] initWithFrame:CGRectZero];
75   [label1 setText: line1];
76   [label2 setText: line2];
77   [label1 setBackgroundColor:[UIColor clearColor]];
78   [label2 setBackgroundColor:[UIColor clearColor]];
79
80   [label1 setFont: [UIFont boldSystemFontOfSize: 17]];
81   [label2 setFont: [UIFont systemFontOfSize: 12]];
82   [label1 sizeToFit];
83   [label2 sizeToFit];
84
85   CGRect r1 = [label1 frame];
86   CGRect r2 = [label2 frame];
87   CGRect r3 = r2;
88
89   CGRect win = [self view].frame;
90   if (win.size.width > 320) {                                   // iPad
91     [label1 setTextAlignment: UITextAlignmentLeft];
92     [label2 setTextAlignment: UITextAlignmentRight];
93     label2.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
94     r3.size.width = win.size.width;
95     r1 = r3;
96     r1.origin.x   += 6;
97     r1.size.width -= 12;
98     r2 = r1;
99
100   } else {                                                      // iPhone
101     r3.size.width = 320; // force it to be flush-left
102     [label1 setTextAlignment: UITextAlignmentLeft];
103     [label2 setTextAlignment: UITextAlignmentLeft];
104     r1.origin.y = -1;    // make it fit in landscape
105     r2.origin.y = r1.origin.y + r1.size.height - 2;
106     r3.size.height = r1.size.height + r2.size.height;
107   }
108   v.autoresizingMask = UIViewAutoresizingFlexibleWidth;
109   [label1 setFrame:r1];
110   [label2 setFrame:r2];
111   [v setFrame:r3];
112
113   [v addSubview:label1];
114   [v addSubview:label2];
115
116   self.navigationItem.titleView = v;
117 }
118
119
120 - (id)initWithNames:(NSArray *)names descriptions:(NSDictionary *)descs;
121 {
122   self = [self init];
123   if (! self) return 0;
124   [self reload:names descriptions:descs];
125   [self makeTitleBar];
126   return self;
127 }
128
129
130 - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tv
131 {
132   int n = countof(list_by_letter);
133   NSMutableArray *a = [NSMutableArray arrayWithCapacity: n];
134   for (int i = 0; i < n; i++) {
135     char s[2];
136     s[0] = (i == 'Z'-'A'+1 ? '#' : i+'A');
137     s[1] = 0;
138     [a addObject: [NSString stringWithCString:s
139                             encoding:NSASCIIStringEncoding]];
140   }
141   return a;
142 }
143
144
145 - (void) reload:(NSArray *)names descriptions:(NSDictionary *)descs
146 {
147   if (descriptions)
148     [descriptions release];
149   descriptions = [descs retain];
150
151   int n = countof(list_by_letter);
152   for (int i = 0; i < n; i++) {
153     list_by_letter[i] = [[NSMutableArray alloc] init];
154   }
155
156   for (NSString *name in names) {
157     int index = ([name cStringUsingEncoding: NSASCIIStringEncoding])[0];
158     if (index >= 'a' && index <= 'z')
159       index -= 'a'-'A';
160     if (index >= 'A' && index <= 'Z')
161       index -= 'A';
162     else
163       index = n-1;
164     [list_by_letter[index] addObject: name];
165   }
166
167   active_section_count = 0;
168   letter_sections = [[[NSMutableArray alloc] init] retain];
169   section_titles = [[[NSMutableArray alloc] init] retain];
170   for (int i = 0; i < n; i++) {
171     if ([list_by_letter[i] count] > 0) {
172       active_section_count++;
173       [letter_sections addObject: list_by_letter[i]];
174       if (i <= 'Z'-'A')
175         [section_titles addObject: [NSString stringWithFormat: @"%c", i+'A']];
176       else
177         [section_titles addObject: @"#"];
178     }
179   }
180   [self.tableView reloadData];
181 }
182
183
184 - (NSString *)tableView:(UITableView *)tv
185               titleForHeaderInSection:(NSInteger)section
186 {
187   return [section_titles objectAtIndex: section];
188 }
189
190 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tv
191 {
192   return active_section_count;
193 }
194
195
196 - (NSInteger)tableView:(UITableView *)tv
197                        numberOfRowsInSection:(NSInteger)section
198 {
199   return [[letter_sections objectAtIndex: section] count];
200 }
201
202 - (NSInteger)tableView:(UITableView *)tv
203              sectionForSectionIndexTitle:(NSString *)title
204                atIndex:(NSInteger) index
205 {
206   int i = 0;
207   for (NSString *sectionTitle in section_titles) {
208     if ([sectionTitle isEqualToString: title])
209       return i;
210     i++;
211   }
212   return -1;
213 }
214
215
216 - (UITableViewCell *)tableView:(UITableView *)tv
217                      cellForRowAtIndexPath:(NSIndexPath *)ip
218 {
219   NSString *id =
220     [[letter_sections objectAtIndex: [ip indexAtPosition: 0]]
221       objectAtIndex: [ip indexAtPosition: 1]];
222   NSString *desc = [descriptions objectForKey:id];
223
224   UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier: id];
225   if (!cell) {
226     cell = [[[UITableViewCell alloc]
227               initWithStyle: (desc
228                               ? UITableViewCellStyleSubtitle
229                               : UITableViewCellStyleDefault)
230               reuseIdentifier: id]
231              autorelease];
232     cell.textLabel.text = id;
233
234     cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
235     if (desc)
236       cell.detailTextLabel.text = desc;
237   }
238   return cell;
239 }
240
241
242 /* Selecting a row launches the saver.
243  */
244 - (void)tableView:(UITableView *)tv
245         didSelectRowAtIndexPath:(NSIndexPath *)ip
246 {
247   UITableViewCell *cell = [tv cellForRowAtIndexPath: ip];
248   SaverRunner *s = 
249     (SaverRunner *) [[UIApplication sharedApplication] delegate];
250   if (! s) return;
251   NSAssert ([s isKindOfClass:[SaverRunner class]], @"not a SaverRunner");
252   [s loadSaver: cell.textLabel.text];
253 }
254
255 /* Selecting a row's Disclosure Button opens the preferences.
256  */
257 - (void)tableView:(UITableView *)tv
258         accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)ip
259 {
260   UITableViewCell *cell = [tv cellForRowAtIndexPath: ip];
261   SaverRunner *s = 
262     (SaverRunner *) [[UIApplication sharedApplication] delegate];
263   if (! s) return;
264   NSAssert ([s isKindOfClass:[SaverRunner class]], @"not a SaverRunner");
265   [s openPreferences: cell.textLabel.text];
266 }
267
268
269 - (void) scrollTo: (NSString *) name
270 {
271   int i = 0;
272   int j = 0;
273   Bool ok = NO;
274   for (NSArray *a in letter_sections) {
275     j = 0;
276     for (NSString *n in a) {
277       ok = [n isEqualToString: name];
278       if (ok) goto DONE;
279       j++;
280     }
281     i++;
282   }
283  DONE:
284   if (ok) {
285     NSIndexPath *ip = [NSIndexPath indexPathForRow: j inSection: i];
286     [self.tableView selectRowAtIndexPath:ip
287                     animated:NO
288                     scrollPosition: UITableViewScrollPositionMiddle];
289   }
290 }
291
292
293 - (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)o
294 {
295   return YES;
296 }
297
298
299 /* We need this to respond to "shake" gestures
300  */
301 - (BOOL)canBecomeFirstResponder
302 {
303   return YES;
304 }
305
306 - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
307 {
308 }
309
310 - (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event
311 {
312 }
313
314
315 /* Shake means load a random screen saver.
316  */
317 - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
318 {
319   if (motion != UIEventSubtypeMotionShake)
320     return;
321   NSMutableArray *a = [NSMutableArray arrayWithCapacity: 200];
322   for (NSArray *sec in letter_sections)
323     for (NSString *s in sec)
324       [a addObject: s];
325   int n = [a count];
326   if (! n) return;
327   NSString *which = [a objectAtIndex: (random() % n)];
328
329   SaverRunner *s = 
330     (SaverRunner *) [[UIApplication sharedApplication] delegate];
331   if (! s) return;
332   NSAssert ([s isKindOfClass:[SaverRunner class]], @"not a SaverRunner");
333   [self scrollTo: which];
334   [s loadSaver: which];
335 }
336
337
338 - (void)dealloc
339 {
340   for (int i = 0; i < countof(list_by_letter); i++)
341     [list_by_letter[i] release];
342   [letter_sections release];
343   [section_titles release];
344   [descriptions release];
345   [super dealloc];
346 }
347
348 @end
349
350
351 #endif // USE_IPHONE -- whole file