summaryrefslogtreecommitdiffstats
path: root/OSX/SaverListController.m
diff options
context:
space:
mode:
authorSimon Rettberg2018-10-16 10:08:48 +0200
committerSimon Rettberg2018-10-16 10:08:48 +0200
commitd3a98cf6cbc3bd0b9efc570f58e8812c03931c18 (patch)
treecbddf8e50f35a9c6e878a5bfe3c6d625d99e12ba /OSX/SaverListController.m
downloadxscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.tar.gz
xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.tar.xz
xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.zip
Original 5.40
Diffstat (limited to 'OSX/SaverListController.m')
-rw-r--r--OSX/SaverListController.m402
1 files changed, 402 insertions, 0 deletions
diff --git a/OSX/SaverListController.m b/OSX/SaverListController.m
new file mode 100644
index 0000000..9377275
--- /dev/null
+++ b/OSX/SaverListController.m
@@ -0,0 +1,402 @@
+/* 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
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ *
+ * This implements the top-level screen-saver selection list in the iOS app.
+ */
+
+#ifdef USE_IPHONE // whole file
+
+
+#import "SaverListController.h"
+#import "SaverRunner.h"
+#import "yarandom.h"
+#import "version.h"
+
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
+
+
+@implementation SaverListController
+
+- (void) titleTapped:(id) sender
+{
+ [[UIApplication sharedApplication]
+ openURL:[NSURL URLWithString:@"https://www.jwz.org/xscreensaver/"]];
+}
+
+
+- (void)makeTitleBar
+{
+ // 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]
+ componentsSeparatedByCharactersInSet:
+ [NSCharacterSet
+ characterSetWithCharactersInString:@" ()-"]];
+ NSString *vers = [a objectAtIndex: 3];
+ NSString *year = [a objectAtIndex: 7];
+
+ NSString *line1 = [@"XScreenSaver " stringByAppendingString: vers];
+ NSString *line2 = [@"\u00A9 " stringByAppendingString:
+ [year stringByAppendingString:
+ @" Jamie Zawinski <jwz@jwz.org>"]];
+
+ UIView *v = [[UIView alloc] initWithFrame:CGRectZero];
+
+ // The "go to web page" button on the right
+
+ UIImage *img = [UIImage imageWithContentsOfFile:
+ [[[NSBundle mainBundle] bundlePath]
+ stringByAppendingPathComponent:
+ @"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
+
+ UILabel *label1 = [[UILabel alloc] initWithFrame:CGRectZero];
+ UILabel *label2 = [[UILabel alloc] initWithFrame:CGRectZero];
+ [label1 setText: line1];
+ [label2 setText: line2];
+ [label1 setBackgroundColor:[UIColor clearColor]];
+ [label2 setBackgroundColor:[UIColor clearColor]];
+
+ [label1 setFont: [UIFont boldSystemFontOfSize: 17]];
+ [label2 setFont: [UIFont systemFontOfSize: 12]];
+ [label1 sizeToFit];
+ [label2 sizeToFit];
+
+ CGRect r1 = [label1 frame];
+ CGRect r2 = [label2 frame];
+ CGRect r3 = r2;
+
+ CGRect win = [self view].frame;
+ 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;
+ r1.origin.x += 6;
+ r1.size.width -= 12;
+ r2 = r1;
+
+ } else { // iPhone
+ 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.autoresizingMask = UIViewAutoresizingFlexibleWidth;
+ [label1 setFrame:r1];
+ [label2 setFrame:r2];
+ [v setFrame:r3];
+
+ [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;
+{
+ self = [self init];
+ if (! self) return 0;
+ [self reload:_names descriptions:_descs search:nil];
+ [self makeTitleBar];
+ return self;
+}
+
+
+- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tv
+{
+ 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;
+ [a addObject: [NSString stringWithCString:s
+ encoding:NSASCIIStringEncoding]];
+ }
+ return a;
+}
+
+
+/* Called when text is typed into the top search bar.
+ */
+- (void)searchBar:(UISearchBar *)bar textDidChange:(NSString *)txt
+{
+ [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++) {
+ list_by_letter[i] = [[NSMutableArray alloc] init];
+ }
+
+ 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';
+ if (index >= 'A' && index <= 'Z')
+ index -= 'A';
+ else
+ index = n-1;
+ [list_by_letter[index] addObject: name];
+ }
+
+ active_section_count = 0;
+ letter_sections = [[[NSMutableArray alloc] init] retain];
+ section_titles = [[[NSMutableArray alloc] init] retain];
+ for (int i = 0; i < n; i++) {
+ if ([list_by_letter[i] count] > 0) {
+ active_section_count++;
+ [letter_sections addObject: list_by_letter[i]];
+ if (i <= 'Z'-'A')
+ [section_titles addObject: [NSString stringWithFormat: @"%c", i+'A']];
+ else
+ [section_titles addObject: @"#"];
+ }
+ }
+ [self.tableView reloadData];
+}
+
+
+- (NSString *)tableView:(UITableView *)tv
+ titleForHeaderInSection:(NSInteger)section
+{
+ return [section_titles objectAtIndex: section];
+}
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tv
+{
+ return active_section_count;
+}
+
+
+- (NSInteger)tableView:(UITableView *)tv
+ numberOfRowsInSection:(NSInteger)section
+{
+ return [[letter_sections objectAtIndex: section] count];
+}
+
+- (NSInteger)tableView:(UITableView *)tv
+ sectionForSectionIndexTitle:(NSString *)title
+ atIndex:(NSInteger) index
+{
+ int i = 0;
+ for (NSString *sectionTitle in section_titles) {
+ if ([sectionTitle isEqualToString: title])
+ return i;
+ i++;
+ }
+ return -1;
+}
+
+
+- (UITableViewCell *)tableView:(UITableView *)tv
+ cellForRowAtIndexPath:(NSIndexPath *)ip
+{
+ NSString *title =
+ [[letter_sections objectAtIndex: [ip indexAtPosition: 0]]
+ objectAtIndex: [ip indexAtPosition: 1]];
+ NSString *desc = [descriptions objectForKey:title];
+
+ NSString *id = @"Cell";
+ UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:id];
+ if (!cell)
+ cell = [[[UITableViewCell alloc]
+ initWithStyle: UITableViewCellStyleSubtitle
+ reuseIdentifier: id]
+ autorelease];
+
+ cell.textLabel.text = title;
+ cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
+ cell.detailTextLabel.text = desc;
+
+ return cell;
+}
+
+
+/* Selecting a row launches the saver.
+ */
+- (void)tableView:(UITableView *)tv
+ didSelectRowAtIndexPath:(NSIndexPath *)ip
+{
+ UITableViewCell *cell = [tv cellForRowAtIndexPath: ip];
+ SaverRunner *s =
+ (SaverRunner *) [[UIApplication sharedApplication] delegate];
+ if (! s) return;
+
+ // 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];
+}
+
+/* Selecting a row's Disclosure Button opens the preferences.
+ */
+- (void)tableView:(UITableView *)tv
+ accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)ip
+{
+ UITableViewCell *cell = [tv cellForRowAtIndexPath: ip];
+ SaverRunner *s =
+ (SaverRunner *) [[UIApplication sharedApplication] delegate];
+ if (! s) return;
+ NSAssert ([s isKindOfClass:[SaverRunner class]], @"not a SaverRunner");
+ [s openPreferences: cell.textLabel.text];
+}
+
+
+- (void) scrollTo: (NSString *) name
+{
+ int i = 0;
+ int j = 0;
+ Bool ok = NO;
+ for (NSArray *a in letter_sections) {
+ j = 0;
+ for (NSString *n in a) {
+ ok = [n isEqualToString: name];
+ if (ok) goto DONE;
+ j++;
+ }
+ i++;
+ }
+ DONE:
+ if (ok) {
+ NSIndexPath *ip = [NSIndexPath indexPathForRow: j inSection: i];
+ [self.tableView selectRowAtIndexPath:ip
+ animated:NO
+ scrollPosition: UITableViewScrollPositionMiddle];
+ }
+}
+
+
+/* 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
+{
+ for (int i = 0; i < countof(list_by_letter); i++)
+ [list_by_letter[i] release];
+ [letter_sections release];
+ [section_titles release];
+ [descriptions release];
+ [super dealloc];
+}
+
+@end
+
+
+#endif // USE_IPHONE -- whole file