summaryrefslogblamecommitdiffstats
path: root/OSX/SaverListController.m
blob: e68387c9f5d5da5964cec2db2fdec726b26cb47b (plain) (tree)
1
                                                                     

































































































































































































































                                                                              

                                                                        














































































































































































                                                                              
/* xscreensaver, Copyright (c) 2012-2018 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++;
      [list_by_letter[i] sortUsingSelector:
                           @selector(localizedCaseInsensitiveCompare:)];
      [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