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