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