From http://www.jwz.org/xscreensaver/xscreensaver-5.32.tar.gz
[xscreensaver] / OSX / XScreenSaverConfigSheet.m
index 12980fa7cf2f6222c15a3dd8394e797ca46dda62..5a6484366df0ca2bb7ca0bd1b5d22ac0c72f9e96 100644 (file)
@@ -1,4 +1,4 @@
-/* xscreensaver, Copyright (c) 2006-2012 Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright (c) 2006-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
@@ -24,6 +24,7 @@
  */
 
 #import "XScreenSaverConfigSheet.h"
+#import "Updater.h"
 
 #import "jwxyz.h"
 #import "InvertedSlider.h"
@@ -48,6 +49,7 @@
 #endif // USE_IPHONE
 
 #undef LABEL_ABOVE_SLIDER
+#define USE_HTML_LABELS
 
 
 #pragma mark XML Parser
@@ -191,6 +193,249 @@ typedef enum { SimpleXMLCommentKind,
 # endif // !USE_PICKER_VIEW
 
 
+# pragma mark Implementing labels with clickable links
+
+#if defined(USE_IPHONE) && defined(USE_HTML_LABELS)
+
+@interface HTMLLabel : UIView <UIWebViewDelegate>
+{
+  NSString *html;
+  UIFont *font;
+  UIWebView *webView;
+}
+
+@property(nonatomic, retain) NSString *html;
+@property(nonatomic, retain) UIWebView *webView;
+
+- (id) initWithHTML:(NSString *)h font:(UIFont *)f;
+- (id) initWithText:(NSString *)t font:(UIFont *)f;
+- (void) setHTML:(NSString *)h;
+- (void) setText:(NSString *)t;
+- (void) sizeToFit;
+
+@end
+
+@implementation HTMLLabel
+
+@synthesize html;
+@synthesize webView;
+
+- (id) initWithHTML:(NSString *)h font:(UIFont *)f
+{
+  self = [super init];
+  if (! self) return 0;
+  font = [f retain];
+  webView = [[UIWebView alloc] init];
+  webView.delegate = self;
+  webView.dataDetectorTypes = UIDataDetectorTypeNone;
+  self.   autoresizingMask = UIViewAutoresizingNone;  // we do it manually
+  webView.autoresizingMask = UIViewAutoresizingNone;
+  webView.scrollView.scrollEnabled = NO; 
+  webView.scrollView.bounces = NO;
+  webView.opaque = NO;
+  [webView setBackgroundColor:[UIColor clearColor]];
+
+  [self addSubview: webView];
+  [self setHTML: h];
+  return self;
+}
+
+- (id) initWithText:(NSString *)t font:(UIFont *)f
+{
+  self = [self initWithHTML:@"" font:f];
+  if (! self) return 0;
+  [self setText: t];
+  return self;
+}
+
+
+- (void) setHTML: (NSString *)h
+{
+  if (! h) return;
+  [h retain];
+  if (html) [html release];
+  html = h;
+  NSString *h2 =
+    [NSString stringWithFormat:
+                @"<!DOCTYPE HTML PUBLIC "
+                   "\"-//W3C//DTD HTML 4.01 Transitional//EN\""
+                   " \"http://www.w3.org/TR/html4/loose.dtd\">"
+                 "<HTML>"
+                  "<HEAD>"
+//                   "<META NAME=\"viewport\" CONTENT=\""
+//                      "width=device-width"
+//                      "initial-scale=1.0;"
+//                      "maximum-scale=1.0;\">"
+                   "<STYLE>"
+                    "<!--\n"
+                      "body {"
+                      " margin: 0; padding: 0; border: 0;"
+                      " font-family: \"%@\";"
+                      " font-size: %.4fpx;"    // Must be "px", not "pt"!
+                      " line-height: %.4fpx;"   // And no spaces before it.
+                      " -webkit-text-size-adjust: none;"
+                      "}"
+                    "\n//-->\n"
+                   "</STYLE>"
+                  "</HEAD>"
+                  "<BODY>"
+                   "%@"
+                  "</BODY>"
+                 "</HTML>",
+              [font fontName],
+              [font pointSize],
+              [font lineHeight],
+              h];
+  [webView stopLoading];
+  [webView loadHTMLString:h2 baseURL:[NSURL URLWithString:@""]];
+}
+
+
+static char *anchorize (const char *url);
+
+- (void) setText: (NSString *)t
+{
+  t = [t stringByTrimmingCharactersInSet:[NSCharacterSet
+                                           whitespaceCharacterSet]];
+  t = [t stringByReplacingOccurrencesOfString:@"&" withString:@"&amp;"];
+  t = [t stringByReplacingOccurrencesOfString:@"<" withString:@"&lt;"];
+  t = [t stringByReplacingOccurrencesOfString:@">" withString:@"&gt;"];
+  t = [t stringByReplacingOccurrencesOfString:@"\n\n" withString:@" <P> "];
+  t = [t stringByReplacingOccurrencesOfString:@"<P>  "
+         withString:@"<P> &nbsp; &nbsp; &nbsp; &nbsp; "];
+  t = [t stringByReplacingOccurrencesOfString:@"\n "
+         withString:@"<BR> &nbsp; &nbsp; &nbsp; &nbsp; "];
+
+  NSString *h = @"";
+  for (NSString *s in
+         [t componentsSeparatedByCharactersInSet:
+              [NSCharacterSet whitespaceAndNewlineCharacterSet]]) {
+    if ([s hasPrefix:@"http://"] ||
+        [s hasPrefix:@"https://"]) {
+      char *anchor = anchorize ([s cStringUsingEncoding:NSUTF8StringEncoding]);
+      NSString *a2 = [NSString stringWithCString: anchor
+                               encoding: NSUTF8StringEncoding];
+      s = [NSString stringWithFormat: @"<A HREF=\"%@\">%@</A><BR>", s, a2];
+      free (anchor);
+    }
+    h = [NSString stringWithFormat: @"%@ %@", h, s];
+  }
+
+  h = [h stringByReplacingOccurrencesOfString:@" <P> " withString:@"<P>"];
+  h = [h stringByReplacingOccurrencesOfString:@"<BR><P>" withString:@"<P>"];
+  h = [h stringByTrimmingCharactersInSet:[NSCharacterSet
+                                           whitespaceAndNewlineCharacterSet]];
+
+  [self setHTML: h];
+}
+
+
+-(BOOL) webView:(UIWebView *)wv
+        shouldStartLoadWithRequest:(NSURLRequest *)req
+        navigationType:(UIWebViewNavigationType)type
+{
+  // Force clicked links to open in Safari, not in this window.
+  if (type == UIWebViewNavigationTypeLinkClicked) {
+    [[UIApplication sharedApplication] openURL:[req URL]];
+    return NO;
+  }
+  return YES;
+}
+
+
+- (void) setFrame: (CGRect)r
+{
+  [super setFrame: r];
+  r.origin.x = 0;
+  r.origin.y = 0;
+  [webView setFrame: r];
+}
+
+
+- (NSString *) stripTags:(NSString *)str
+{
+  NSString *result = @"";
+
+  // Add newlines.
+  str = [str stringByReplacingOccurrencesOfString:@"<P>"
+             withString:@"<BR><BR>"
+             options:NSCaseInsensitiveSearch
+             range:NSMakeRange(0, [str length])];
+  str = [str stringByReplacingOccurrencesOfString:@"<BR>"
+             withString:@"\n"
+             options:NSCaseInsensitiveSearch
+             range:NSMakeRange(0, [str length])];
+
+  // Remove HREFs.
+  for (NSString *s in [str componentsSeparatedByString: @"<"]) {
+    NSRange r = [s rangeOfString:@">"];
+    if (r.length > 0)
+      s = [s substringFromIndex: r.location + r.length];
+    result = [result stringByAppendingString: s];
+  }
+
+  // Compress internal horizontal whitespace.
+  str = result;
+  result = @"";
+  for (NSString *s in [str componentsSeparatedByCharactersInSet:
+                             [NSCharacterSet whitespaceCharacterSet]]) {
+    if ([result length] == 0)
+      result = s;
+    else if ([s length] > 0)
+      result = [NSString stringWithFormat: @"%@ %@", result, s];
+  }
+
+  return result;
+}
+
+
+- (void) sizeToFit
+{
+  CGRect r = [self frame];
+
+  /* It would be sensible to just ask the UIWebView how tall the page is,
+     instead of hoping that NSString and UIWebView measure fonts and do
+     wrapping in exactly the same way, but since UIWebView is asynchronous,
+     we'd have to wait for the document to load first, e.g.:
+
+       - Start the document loading;
+       - return a default height to use for the UITableViewCell;
+       - wait for the webViewDidFinishLoad delegate method to fire;
+       - then force the UITableView to reload, to pick up the new height.
+
+     But I couldn't make that work.
+   */
+# if 0
+  r.size.height = [[webView
+                     stringByEvaluatingJavaScriptFromString:
+                       @"document.body.offsetHeight"]
+                    doubleValue];
+# else
+  NSString *text = [self stripTags: html];
+  CGSize s = r.size;
+  s.height = 999999;
+  s = [text sizeWithFont: font
+            constrainedToSize: s
+            lineBreakMode:NSLineBreakByWordWrapping];
+  r.size.height = s.height;
+# endif
+
+  [self setFrame: r];
+}
+
+
+- (void) dealloc
+{
+  [html release];
+  [font release];
+  [webView release];
+  [super dealloc];
+}
+
+@end
+
+#endif // USE_IPHONE && USE_HTML_LABELS
+
 
 @interface XScreenSaverConfigSheet (Private)
 
@@ -219,6 +464,7 @@ static void layout_group (NSView *group, BOOL horiz_p);
 # define LEFT_LABEL_WIDTH 70   // width of all left labels
 # define LINE_SPACING     10   // leading between each line
 
+# define FONT_SIZE       17   // Magic hardcoded UITableView font size.
 
 #pragma mark Talking to the resource database
 
@@ -229,7 +475,7 @@ static void layout_group (NSView *group, BOOL horiz_p);
    instead, so transform keys to "SAVERNAME.KEY".
 
    NOTE: This is duplicated in PrefsReader.m, cause I suck.
-*/
+ */
 - (NSString *) makeKey:(NSString *)key
 {
 # ifdef USE_IPHONE
@@ -306,6 +552,22 @@ static void layout_group (NSView *group, BOOL horiz_p);
 }
 
 
+- (NSUserDefaultsController *)controllerForKey:(NSString *)key
+{
+  static NSDictionary *a = 0;
+  if (! a) {
+    a = UPDATER_DEFAULTS;
+    [a retain];
+  }
+  if ([a objectForKey:key])
+    // These preferences are global to all xscreensavers.
+    return globalDefaultsController;
+  else
+    // All other preferences are per-saver.
+    return userDefaultsController;
+}
+
+
 #ifdef USE_IPHONE
 
 // Called when a slider is bonked.
@@ -315,11 +577,17 @@ static void layout_group (NSView *group, BOOL horiz_p);
   if ([active_text_field canResignFirstResponder])
     [active_text_field resignFirstResponder];
   NSString *pref_key = [pref_keys objectAtIndex: [sender tag]];
-  double v = [sender value];
-  if (v == (int) v)
-    [userDefaultsController setInteger:v forKey:pref_key];
-  else
-    [userDefaultsController setDouble:v forKey:pref_key];
+
+  // Hacky API. See comment in InvertedSlider.m.
+  double v = ([sender isKindOfClass: [InvertedSlider class]]
+              ? [(InvertedSlider *) sender transformedValue]
+              : [sender value]);
+
+  [[self controllerForKey:pref_key]
+    setObject:((v == (int) v)
+               ? [NSNumber numberWithInt:(int) v]
+               : [NSNumber numberWithDouble: v])
+    forKey:pref_key];
 }
 
 // Called when a checkbox/switch is bonked.
@@ -330,7 +598,7 @@ static void layout_group (NSView *group, BOOL horiz_p);
     [active_text_field resignFirstResponder];
   NSString *pref_key = [pref_keys objectAtIndex: [sender tag]];
   NSString *v = ([sender isOn] ? @"true" : @"false");
-  [userDefaultsController setObject:v forKey:pref_key];
+  [[self controllerForKey:pref_key] setObject:v forKey:pref_key];
 }
 
 # ifdef USE_PICKER_VIEW
@@ -352,7 +620,7 @@ static void layout_group (NSView *group, BOOL horiz_p);
 //NSString *label    = [a objectAtIndex:0];
   NSString *pref_key = [a objectAtIndex:1];
   NSObject *pref_val = [a objectAtIndex:2];
-  [userDefaultsController setObject:pref_val forKey:pref_key];
+  [[self controllerForKey:pref_key] setObject:pref_val forKey:pref_key];
 }
 # else  // !USE_PICKER_VIEW
 
@@ -366,7 +634,7 @@ static void layout_group (NSView *group, BOOL horiz_p);
   NSArray *item = [[sender items] objectAtIndex: [sender index]];
   NSString *pref_key = [item objectAtIndex:1];
   NSObject *pref_val = [item objectAtIndex:2];
-  [userDefaultsController setObject:pref_val forKey:pref_key];
+  [[self controllerForKey:pref_key] setObject:pref_val forKey:pref_key];
 }
 
 - (BOOL)textFieldShouldBeginEditing:(UITextField *)tf
@@ -379,7 +647,7 @@ static void layout_group (NSView *group, BOOL horiz_p);
 {
   NSString *pref_key = [pref_keys objectAtIndex: [tf tag]];
   NSString *txt = [tf text];
-  [userDefaultsController setObject:txt forKey:pref_key];
+  [[self controllerForKey:pref_key] setObject:txt forKey:pref_key];
 }
 
 - (BOOL)textFieldShouldReturn:(UITextField *)tf
@@ -398,15 +666,18 @@ static void layout_group (NSView *group, BOOL horiz_p);
 
 - (void) okAction:(NSObject *)arg
 {
-  [userDefaultsController commitEditing];
-  [userDefaultsController save:self];
+  [userDefaultsController   commitEditing];
+  [globalDefaultsController commitEditing];
+  [userDefaultsController   save:self];
+  [globalDefaultsController save:self];
   [NSApp endSheet:self returnCode:NSOKButton];
   [self close];
 }
 
 - (void) cancelAction:(NSObject *)arg
 {
-  [userDefaultsController revert:self];
+  [userDefaultsController   revert:self];
+  [globalDefaultsController revert:self];
   [NSApp endSheet:self returnCode:NSCancelButton];
   [self close];
 }
@@ -416,12 +687,13 @@ static void layout_group (NSView *group, BOOL horiz_p);
 - (void) resetAction:(NSObject *)arg
 {
 # ifndef USE_IPHONE
-  [userDefaultsController revertToInitialValues:self];
+  [userDefaultsController   revertToInitialValues:self];
+  [globalDefaultsController revertToInitialValues:self];
 # else  // USE_IPHONE
 
   for (NSString *key in defaultOptions) {
     NSObject *val = [defaultOptions objectForKey:key];
-    [userDefaultsController setObject:val forKey:key];
+    [[self controllerForKey:key] setObject:val forKey:key];
   }
 
   for (UIControl *ctl in pref_ctls) {
@@ -439,6 +711,7 @@ static void layout_group (NSView *group, BOOL horiz_p);
 - (void) bindResource:(NSObject *)control key:(NSString *)pref_key
          reload:(BOOL)reload_p
 {
+  NSUserDefaultsController *prefs = [self controllerForKey:pref_key];
 # ifndef USE_IPHONE
   NSString *bindto = ([control isKindOfClass:[NSPopUpButton class]]
                       ? @"selectedObject"
@@ -446,12 +719,12 @@ static void layout_group (NSView *group, BOOL horiz_p);
                          ? @"selectedIndex"
                          : @"value"));
   [control bind:bindto
-       toObject:userDefaultsController
+       toObject:prefs
     withKeyPath:[@"values." stringByAppendingString: pref_key]
         options:nil];
 # else  // USE_IPHONE
   SEL sel;
-  NSObject *val = [userDefaultsController objectForKey:pref_key];
+  NSObject *val = [prefs objectForKey:pref_key];
   NSString *sval = 0;
   double dval = 0;
 
@@ -471,7 +744,11 @@ static void layout_group (NSView *group, BOOL horiz_p);
 
   if ([control isKindOfClass:[UISlider class]]) {
     sel = @selector(sliderAction:);
-    [(UISlider *) control setValue: dval];
+    // Hacky API. See comment in InvertedSlider.m.
+    if ([control isKindOfClass:[InvertedSlider class]])
+      [(InvertedSlider *) control setTransformedValue: dval];
+    else
+      [(UISlider *) control setValue: dval];
   } else if ([control isKindOfClass:[UISwitch class]]) {
     sel = @selector(switchAction:);
     [(UISwitch *) control setOn: ((int) dval != 0)];
@@ -512,7 +789,7 @@ static void layout_group (NSView *group, BOOL horiz_p);
 # endif // USE_IPHONE
 
 # if 0
-  NSObject *def = [[userDefaultsController defaults] objectForKey:pref_key];
+  NSObject *def = [[prefs defaults] objectForKey:pref_key];
   NSString *s = [NSString stringWithFormat:@"bind: \"%@\"", pref_key];
   s = [s stringByPaddingToLength:18 withString:@" " startingAtIndex:0];
   s = [NSString stringWithFormat:@"%@ = \"%@\"", s, def];
@@ -546,9 +823,8 @@ unwrap (NSString *text)
   // Unwrap lines: delete \n but do not delete \n\n.
   //
   NSArray *lines = [text componentsSeparatedByString:@"\n"];
-  int nlines = [lines count];
+  NSUInteger i, nlines = [lines count];
   BOOL eolp = YES;
-  int i;
 
   text = @"\n";      // start with one blank line
 
@@ -681,6 +957,8 @@ anchorize (const char *url)
 }
 
 
+#if !defined(USE_IPHONE) || !defined(USE_HTML_LABELS)
+
 /* Converts any http: URLs in the given text field to clickable links.
  */
 static void
@@ -765,6 +1043,9 @@ hreffify (NSText *nstext)
 # endif
 }
 
+#endif /* !USE_IPHONE || !USE_HTML_LABELS */
+
+
 
 #pragma mark Creating controls from XML
 
@@ -779,7 +1060,7 @@ hreffify (NSText *nstext)
 - (void) parseAttrs:(NSMutableDictionary *)dict node:(NSXMLNode *)node
 {
   NSArray *attrs = [(NSXMLElement *) node attributes];
-  int n = [attrs count];
+  NSUInteger n = [attrs count];
   int i;
   
   // For each key in the dictionary, fill in the dict with the corresponding
@@ -812,6 +1093,26 @@ hreffify (NSText *nstext)
     if ([val length] == 0)
       [dict removeObjectForKey:key];
   }
+
+# ifdef USE_IPHONE
+  // Kludge for starwars.xml:
+  // If there is a "_low-label" and no "_label", but "_low-label" contains
+  // spaces, divide them.
+  NSString *lab = [dict objectForKey:@"_label"];
+  NSString *low = [dict objectForKey:@"_low-label"];
+  if (low && !lab) {
+    NSArray *split =
+      [[[low stringByTrimmingCharactersInSet:
+               [NSCharacterSet whitespaceAndNewlineCharacterSet]]
+         componentsSeparatedByString: @"  "]
+        filteredArrayUsingPredicate:
+          [NSPredicate predicateWithFormat:@"length > 0"]];
+    if (split && [split count] == 2) {
+      [dict setValue:[split objectAtIndex:0] forKey:@"_label"];
+      [dict setValue:[split objectAtIndex:1] forKey:@"_low-label"];
+    }
+  }
+# endif // USE_IPHONE
 }
 
 
@@ -819,12 +1120,10 @@ hreffify (NSText *nstext)
  */
 - (NSString *) parseXScreenSaverTag:(NSXMLNode *)node
 {
-  NSMutableDictionary *dict =
-  [NSMutableDictionary dictionaryWithObjectsAndKeys:
-    @"", @"name",
-    @"", @"_label",
-    @"", @"gl",
-    nil];
+  NSMutableDictionary *dict = [@{ @"name":   @"",
+                                  @"_label": @"",
+                                  @"gl":     @"" }
+                                mutableCopy];
   [self parseAttrs:dict node:node];
   NSString *name  = [dict objectForKey:@"name"];
   NSString *label = [dict objectForKey:@"_label"];
@@ -856,7 +1155,8 @@ hreffify (NSText *nstext)
                  [NSCharacterSet whitespaceAndNewlineCharacterSet]]];
   [lab setBackgroundColor:[UIColor clearColor]];
   [lab setNumberOfLines:0]; // unlimited
-  [lab setLineBreakMode:UILineBreakModeWordWrap];
+  // [lab setLineBreakMode:UILineBreakModeWordWrap];
+  [lab setLineBreakMode:NSLineBreakByTruncatingHead];
   [lab setAutoresizingMask: (UIViewAutoresizingFlexibleWidth |
                              UIViewAutoresizingFlexibleHeight)];
 # endif // USE_IPHONE
@@ -868,13 +1168,11 @@ hreffify (NSText *nstext)
  */
 - (void) makeCheckbox:(NSXMLNode *)node on:(NSView *)parent
 {
-  NSMutableDictionary *dict =
-    [NSMutableDictionary dictionaryWithObjectsAndKeys:
-      @"", @"id",
-      @"", @"_label",
-      @"", @"arg-set",
-      @"", @"arg-unset",
-      nil];
+  NSMutableDictionary *dict = [@{ @"id":       @"",
+                                  @"_label":    @"",
+                                  @"arg-set":   @"",
+                                  @"arg-unset": @"" }
+                                mutableCopy];
   [self parseAttrs:dict node:node];
   NSString *label     = [dict objectForKey:@"_label"];
   NSString *arg_set   = [dict objectForKey:@"arg-set"];
@@ -934,22 +1232,20 @@ hreffify (NSText *nstext)
 /* Creates the number selection control described by the given XML node.
    If "type=slider", it's an NSSlider.
    If "type=spinbutton", it's a text field with up/down arrows next to it.
-*/
+ */
 - (void) makeNumberSelector:(NSXMLNode *)node on:(NSView *)parent
 {
-  NSMutableDictionary *dict =
-  [NSMutableDictionary dictionaryWithObjectsAndKeys:
-    @"", @"id",
-    @"", @"_label",
-    @"", @"_low-label",
-    @"", @"_high-label",
-    @"", @"type",
-    @"", @"arg",
-    @"", @"low",
-    @"", @"high",
-    @"", @"default",
-    @"", @"convert",
-    nil];
+  NSMutableDictionary *dict = [@{ @"id":          @"",
+                                  @"_label":      @"",
+                                  @"_low-label":  @"",
+                                  @"_high-label": @"",
+                                  @"type":        @"",
+                                  @"arg":         @"",
+                                  @"low":         @"",
+                                  @"high":        @"",
+                                  @"default":     @"",
+                                  @"convert":     @"" }
+                                mutableCopy];
   [self parseAttrs:dict node:node];
   NSString *label      = [dict objectForKey:@"_label"];
   NSString *low_label  = [dict objectForKey:@"_low-label"];
@@ -1059,7 +1355,9 @@ hreffify (NSText *nstext)
       [lab setFrame:rect];
       [self placeChild:lab on:parent];
 # else  // USE_IPHONE
-      [lab setTextAlignment: UITextAlignmentRight];
+      [lab setTextAlignment: NSTextAlignmentRight];
+      // Sometimes rotation screws up truncation.
+      [lab setLineBreakMode:NSLineBreakByClipping];
       [self placeChild:lab on:parent right:(label ? YES : NO)];
 # endif // USE_IPHONE
 
@@ -1094,6 +1392,10 @@ hreffify (NSText *nstext)
       // Make right label be same height as slider.
       rect.size.height = [slider frame].size.height;
       [lab setFrame:rect];
+# ifdef USE_IPHONE
+      // Sometimes rotation screws up truncation.
+      [lab setLineBreakMode:NSLineBreakByClipping];
+# endif
       [self placeChild:lab on:parent right:YES];
       [lab release];
      }
@@ -1221,11 +1523,11 @@ set_menu_item_object (NSMenuItem *item, NSObject *obj)
 
 
 /* Creates the popup menu described by the given XML node (and its children).
-*/
+ */
 - (void) makeOptionMenu:(NSXMLNode *)node on:(NSView *)parent
 {
   NSArray *children = [node children];
-  int i, count = [children count];
+  NSUInteger i, count = [children count];
 
   if (count <= 0) {
     NSAssert1 (0, @"no menu items in \"%@\"", [node name]);
@@ -1234,10 +1536,7 @@ set_menu_item_object (NSMenuItem *item, NSObject *obj)
 
   // get the "id" attribute off the <select> tag.
   //
-  NSMutableDictionary *dict =
-    [NSMutableDictionary dictionaryWithObjectsAndKeys:
-      @"", @"id",
-      nil];
+  NSMutableDictionary *dict = [@{ @"id": @"", } mutableCopy];
   [self parseAttrs:dict node:node];
   
   NSRect rect;
@@ -1284,12 +1583,10 @@ set_menu_item_object (NSMenuItem *item, NSObject *obj)
 
     // get the "id", "_label", and "arg-set" attrs off of the <option> tags.
     //
-    NSMutableDictionary *dict2 =
-      [NSMutableDictionary dictionaryWithObjectsAndKeys:
-        @"", @"id",
-        @"", @"_label",
-        @"", @"arg-set",
-        nil];
+    NSMutableDictionary *dict2 = [@{ @"id":      @"",
+                                     @"_label":  @"",
+                                     @"arg-set": @"" }
+                                   mutableCopy];
     [self parseAttrs:dict2 node:child];
     NSString *label   = [dict2 objectForKey:@"_label"];
     NSString *arg_set = [dict2 objectForKey:@"arg-set"];
@@ -1413,7 +1710,7 @@ set_menu_item_object (NSMenuItem *item, NSObject *obj)
   // Store the items for this picker in the picker_values array.
   // This is so fucking stupid.
 
-  int menu_number = [pref_keys count] - 1;
+  unsigned long menu_number = [pref_keys count] - 1;
   if (! picker_values)
     picker_values = [[NSMutableArray arrayWithCapacity:menu_number] retain];
   while ([picker_values count] <= menu_number)
@@ -1426,13 +1723,11 @@ set_menu_item_object (NSMenuItem *item, NSObject *obj)
   [self placeSeparator];
 
   i = 0;
-  for (NSArray *item in items) {
-    RadioButton *b = [[RadioButton alloc] initWithIndex:
+  for (__attribute__((unused)) NSArray *item in items) {
+    RadioButton *b = [[RadioButton alloc] initWithIndex: (int)i
                                           items:items];
-    [b setFont:[NSFont boldSystemFontOfSize:
-                         // #### Fucking hardcoded UITableView font size BS!
-                         17 // [NSFont systemFontSize]
-                ]];
+    [b setLineBreakMode:NSLineBreakByTruncatingHead];
+    [b setFont:[NSFont boldSystemFontOfSize: FONT_SIZE]];
     [self placeChild:b on:parent];
     i++;
   }
@@ -1452,7 +1747,7 @@ set_menu_item_object (NSMenuItem *item, NSObject *obj)
 {
   NSString *text = nil;
   NSArray *children = [node children];
-  int i, count = [children count];
+  NSUInteger i, count = [children count];
 
   for (i = 0; i < count; i++) {
     NSXMLNode *child = [children objectAtIndex:i];
@@ -1479,11 +1774,25 @@ set_menu_item_object (NSMenuItem *item, NSObject *obj)
   hreffify (lab);
   boldify (lab);
   [lab sizeToFit];
+
 # else  // USE_IPHONE
+
+#  ifndef USE_HTML_LABELS
+
   UILabel *lab = [self makeLabel:text];
   [lab setFont:[NSFont systemFontOfSize: [NSFont systemFontSize]]];
   hreffify (lab);
+
+#  else  // USE_HTML_LABELS
+  HTMLLabel *lab = [[HTMLLabel alloc] 
+                     initWithText:text
+                     font:[NSFont systemFontOfSize: [NSFont systemFontSize]]];
+  [lab setFrame:rect];
+  [lab sizeToFit];
+#  endif // USE_HTML_LABELS
+
   [self placeSeparator];
+
 # endif // USE_IPHONE
 
   [self placeChild:lab on:parent];
@@ -1492,18 +1801,16 @@ set_menu_item_object (NSMenuItem *item, NSObject *obj)
 
 
 /* Creates the NSTextField described by the given XML node.
-*/
+ */
 - (void) makeTextField: (NSXMLNode *)node
                     on: (NSView *)parent
              withLabel: (BOOL) label_p
             horizontal: (BOOL) horiz_p
 {
-  NSMutableDictionary *dict =
-  [NSMutableDictionary dictionaryWithObjectsAndKeys:
-    @"", @"id",
-    @"", @"_label",
-    @"", @"arg",
-    nil];
+  NSMutableDictionary *dict = [@{ @"id":     @"",
+                                  @"_label": @"",
+                                  @"arg":    @"" }
+                                mutableCopy];
   [self parseAttrs:dict node:node];
   NSString *label = [dict objectForKey:@"_label"];
   NSString *arg   = [dict objectForKey:@"arg"];
@@ -1536,11 +1843,10 @@ set_menu_item_object (NSMenuItem *item, NSObject *obj)
 
   txt.adjustsFontSizeToFitWidth = YES;
   txt.textColor = [UIColor blackColor];
-  // #### Fucking hardcoded UITableView font size BS!
-  txt.font = [UIFont systemFontOfSize: 17];
+  txt.font = [UIFont systemFontOfSize: FONT_SIZE];
   txt.placeholder = @"";
   txt.borderStyle = UITextBorderStyleRoundedRect;
-  txt.textAlignment = UITextAlignmentRight;
+  txt.textAlignment = NSTextAlignmentRight;
   txt.keyboardType = UIKeyboardTypeDefault;  // Full kbd
   txt.autocorrectionType = UITextAutocorrectionTypeNo;
   txt.autocapitalizationType = UITextAutocapitalizationTypeNone;
@@ -1570,7 +1876,7 @@ set_menu_item_object (NSMenuItem *item, NSObject *obj)
 
 /* Creates the NSTextField described by the given XML node,
    and hooks it up to a Choose button and a file selector widget.
-*/
+ */
 - (void) makeFileSelector: (NSXMLNode *)node
                        on: (NSView *)parent
                  dirsOnly: (BOOL) dirsOnly
@@ -1578,12 +1884,10 @@ set_menu_item_object (NSMenuItem *item, NSObject *obj)
                  editable: (BOOL) editable_p
 {
 # ifndef USE_IPHONE    // No files. No selectors.
-  NSMutableDictionary *dict =
-  [NSMutableDictionary dictionaryWithObjectsAndKeys:
-    @"", @"id",
-    @"", @"_label",
-    @"", @"arg",
-    nil];
+  NSMutableDictionary *dict = [@{ @"id":     @"",
+                                  @"_label": @"",
+                                  @"arg":    @"" }
+                                mutableCopy];
   [self parseAttrs:dict node:node];
   NSString *label = [dict objectForKey:@"_label"];
   NSString *arg   = [dict objectForKey:@"arg"];
@@ -1837,37 +2141,28 @@ find_text_field_of_button (NSButton *button)
   // </select>
 
   node2 = [[NSXMLElement alloc] initWithName:@"select"];
-  [node2 setAttributesAsDictionary:
-          [NSDictionary dictionaryWithObjectsAndKeys:
-                        @"textMode",           @"id",
-                        nil]];
+  [node2 setAttributesAsDictionary:@{ @"id": @"textMode" }];
 
   NSXMLNode *node3 = [[NSXMLElement alloc] initWithName:@"option"];
   [node3 setAttributesAsDictionary:
-          [NSDictionary dictionaryWithObjectsAndKeys:
-                        @"date",                      @"id",
-                        @"-text-mode date",           @"arg-set",
-                        @"Display the date and time", @"_label",
-                        nil]];
+           @{ @"id":     @"date",
+              @"arg-set": @"-text-mode date",
+              @"_label":  @"Display the date and time" }];
   [node3 setParent: node2];
   //[node3 release];
 
   node3 = [[NSXMLElement alloc] initWithName:@"option"];
   [node3 setAttributesAsDictionary:
-          [NSDictionary dictionaryWithObjectsAndKeys:
-                        @"text",                      @"id",
-                        @"-text-mode literal",        @"arg-set",
-                        @"Display static text",       @"_label",
-                        nil]];
+           @{ @"id":      @"text",
+              @"arg-set": @"-text-mode literal",
+              @"_label":  @"Display static text" }];
   [node3 setParent: node2];
   //[node3 release];
 
   node3 = [[NSXMLElement alloc] initWithName:@"option"];
   [node3 setAttributesAsDictionary:
-          [NSDictionary dictionaryWithObjectsAndKeys:
-                        @"url",                           @"id",
-                        @"Display the contents of a URL", @"_label",
-                        nil]];
+           @{ @"id":     @"url",                           
+              @"_label": @"Display the contents of a URL" }];
   [node3 setParent: node2];
   //[node3 release];
 
@@ -1879,13 +2174,12 @@ find_text_field_of_button (NSButton *button)
   //  <string id="textLiteral" _label="" arg-set="-text-literal %"/>
   node2 = [[NSXMLElement alloc] initWithName:@"string"];
   [node2 setAttributesAsDictionary:
-          [NSDictionary dictionaryWithObjectsAndKeys:
-                        @"textLiteral",        @"id",
-                        @"-text-literal %",    @"arg",
+           @{ @"id":     @"textLiteral",
+              @"arg":    @"-text-literal %",
 # ifdef USE_IPHONE
-                        @"Text to display",    @"_label",
+              @"_label": @"Text to display"
 # endif
-                        nil]];
+            }];
   [self makeTextField:node2 on:rgroup 
 # ifndef USE_IPHONE
         withLabel:NO
@@ -1902,17 +2196,15 @@ find_text_field_of_button (NSButton *button)
            toObject:[matrix cellAtRow:1 column:0]
            withKeyPath:@"value"
            options:nil];
-*/
+ */
 
 
 # ifndef USE_IPHONE
   //  <file id="textFile" _label="" arg-set="-text-file %"/>
   node2 = [[NSXMLElement alloc] initWithName:@"string"];
   [node2 setAttributesAsDictionary:
-          [NSDictionary dictionaryWithObjectsAndKeys:
-                        @"textFile",           @"id",
-                        @"-text-file %",       @"arg",
-                        nil]];
+           @{ @"id":  @"textFile",
+              @"arg": @"-text-file %" }];
   [self makeFileSelector:node2 on:rgroup
         dirsOnly:NO withLabel:NO editable:NO];
 # endif // !USE_IPHONE
@@ -1922,13 +2214,12 @@ find_text_field_of_button (NSButton *button)
   //  <string id="textURL" _label="" arg-set="text-url %"/>
   node2 = [[NSXMLElement alloc] initWithName:@"string"];
   [node2 setAttributesAsDictionary:
-          [NSDictionary dictionaryWithObjectsAndKeys:
-                        @"textURL",            @"id",
-                        @"-text-url %",        @"arg",
+           @{ @"id":     @"textURL",            
+              @"arg":    @"-text-url %",
 # ifdef USE_IPHONE
-                        @"URL to display",     @"_label",
+              @"_label": @"URL to display",     
 # endif
-                        nil]];
+            }];
   [self makeTextField:node2 on:rgroup 
 # ifndef USE_IPHONE
         withLabel:NO
@@ -1944,10 +2235,9 @@ find_text_field_of_button (NSButton *button)
     //  <string id="textProgram" _label="" arg-set="text-program %"/>
     node2 = [[NSXMLElement alloc] initWithName:@"string"];
     [node2 setAttributesAsDictionary:
-            [NSDictionary dictionaryWithObjectsAndKeys:
-                          @"textProgram",        @"id",
-                          @"-text-program %",    @"arg",
-                          nil]];
+             @{ @"id":   @"textProgram",
+                 @"arg": @"-text-program %",
+              }];
     [self makeTextField:node2 on:rgroup withLabel:NO horizontal:NO];
   }
 
@@ -2011,7 +2301,6 @@ find_text_field_of_button (NSButton *button)
 
 - (void) makeImageLoaderControlBox:(NSXMLNode *)node on:(NSView *)parent
 {
-# ifndef USE_IPHONE
   /*
     [x]  Grab desktop images
     [ ]  Choose random image:
@@ -2026,34 +2315,43 @@ find_text_field_of_button (NSButton *button)
 
   NSXMLElement *node2;
 
+# ifndef USE_IPHONE
+#  define SCREENS "Grab desktop images"
+#  define PHOTOS  "Choose random images"
+# else
+#  define SCREENS "Grab screenshots"
+#  define PHOTOS  "Use photo library"
+# endif
+
   node2 = [[NSXMLElement alloc] initWithName:@"boolean"];
   [node2 setAttributesAsDictionary:
-          [NSDictionary dictionaryWithObjectsAndKeys:
-                        @"grabDesktopImages",   @"id",
-                        @"Grab desktop images", @"_label",
-                        @"-no-grab-desktop",    @"arg-unset",
-                        nil]];
+           @{ @"id":        @"grabDesktopImages",
+              @"_label":    @ SCREENS,
+              @"arg-unset": @"-no-grab-desktop",
+            }];
   [self makeCheckbox:node2 on:parent];
 
   node2 = [[NSXMLElement alloc] initWithName:@"boolean"];
   [node2 setAttributesAsDictionary:
-          [NSDictionary dictionaryWithObjectsAndKeys:
-                        @"chooseRandomImages",    @"id",
-                        @"Choose random images",  @"_label",
-                        @"-choose-random-images", @"arg-set",
-                        nil]];
+           @{ @"id":      @"chooseRandomImages",
+              @"_label":  @ PHOTOS,
+              @"arg-set": @"-choose-random-images",
+            }];
   [self makeCheckbox:node2 on:parent];
 
   node2 = [[NSXMLElement alloc] initWithName:@"string"];
   [node2 setAttributesAsDictionary:
-          [NSDictionary dictionaryWithObjectsAndKeys:
-                        @"imageDirectory",     @"id",
-                        @"Images from:",       @"_label",
-                        @"-image-directory %", @"arg",
-                        nil]];
+           @{ @"id":     @"imageDirectory",
+              @"_label": @"Images from:",
+              @"arg":    @"-image-directory %",
+            }];
   [self makeFileSelector:node2 on:parent
         dirsOnly:YES withLabel:YES editable:YES];
 
+# undef SCREENS
+# undef PHOTOS
+
+# ifndef USE_IPHONE
   // Add a second, explanatory label below the file/URL selector.
 
   LABEL *lab2 = 0;
@@ -2066,6 +2364,109 @@ find_text_field_of_button (NSButton *button)
   r2.origin.y += 14;
   [lab2 setFrameOrigin:r2.origin];
   [lab2 release];
+# endif // USE_IPHONE
+}
+
+
+- (void) makeUpdaterControlBox:(NSXMLNode *)node on:(NSView *)parent
+{
+# ifndef USE_IPHONE
+  /*
+    [x]  Check for Updates  [ Monthly ]
+
+  <hgroup>
+   <boolean id="automaticallyChecksForUpdates"
+            _label="Automatically check for updates"
+            arg-unset="-no-automaticallyChecksForUpdates" />
+   <select id="updateCheckInterval">
+    <option="hourly"  _label="Hourly" arg-set="-updateCheckInterval 3600"/>
+    <option="daily"   _label="Daily"  arg-set="-updateCheckInterval 86400"/>
+    <option="weekly"  _label="Weekly" arg-set="-updateCheckInterval 604800"/>
+    <option="monthly" _label="Monthly" arg-set="-updateCheckInterval 2629800"/>
+   </select>
+  </hgroup>
+   */
+
+  // <hgroup>
+
+  NSRect rect;
+  rect.size.width = rect.size.height = 1;
+  rect.origin.x = rect.origin.y = 0;
+  NSView *group = [[NSView alloc] initWithFrame:rect];
+
+  NSXMLElement *node2;
+
+  // <boolean ...>
+
+  node2 = [[NSXMLElement alloc] initWithName:@"boolean"];
+  [node2 setAttributesAsDictionary:
+           @{ @"id":        @SUSUEnableAutomaticChecksKey,
+              @"_label":    @"Automatically check for updates",
+              @"arg-unset": @"-no-" SUSUEnableAutomaticChecksKey,
+            }];
+  [self makeCheckbox:node2 on:group];
+
+  // <select ...>
+
+  node2 = [[NSXMLElement alloc] initWithName:@"select"];
+  [node2 setAttributesAsDictionary:
+           @{ @"id": @SUScheduledCheckIntervalKey }];
+
+  //   <option ...>
+
+  NSXMLNode *node3 = [[NSXMLElement alloc] initWithName:@"option"];
+  [node3 setAttributesAsDictionary:
+           @{ @"id":      @"hourly",
+              @"arg-set": @"-" SUScheduledCheckIntervalKey " 3600",
+              @"_label":  @"Hourly" }];
+  [node3 setParent: node2];
+  //[node3 release];
+
+  node3 = [[NSXMLElement alloc] initWithName:@"option"];
+  [node3 setAttributesAsDictionary:
+           @{ @"id":      @"daily",
+              @"arg-set": @"-" SUScheduledCheckIntervalKey " 86400",
+              @"_label":  @"Daily" }];
+  [node3 setParent: node2];
+  //[node3 release];
+
+  node3 = [[NSXMLElement alloc] initWithName:@"option"];
+  [node3 setAttributesAsDictionary:
+           @{ @"id": @"weekly",
+           // @"arg-set": @"-" SUScheduledCheckIntervalKey " 604800",
+              @"_label": @"Weekly",
+            }];
+  [node3 setParent: node2];
+  //[node3 release];
+
+  node3 = [[NSXMLElement alloc] initWithName:@"option"];
+  [node3 setAttributesAsDictionary:
+           @{ @"id":      @"monthly",
+              @"arg-set": @"-" SUScheduledCheckIntervalKey " 2629800",
+              @"_label":  @"Monthly",
+             }];
+  [node3 setParent: node2];
+  //[node3 release];
+
+  // </option>
+  [self makeOptionMenu:node2 on:group];
+
+  // </hgroup>
+  layout_group (group, TRUE);
+
+  rect.size.width = rect.size.height = 0;
+  NSBox *box = [[NSBox alloc] initWithFrame:rect];
+  [box setTitlePosition:NSNoTitle];
+  [box setBorderType:NSNoBorder];
+  [box setContentViewMargins:rect.size];
+  [box setContentView:group];
+  [box sizeToFit];
+
+  [self placeChild:box on:parent];
+
+  [group release];
+  [box release];
+
 # endif // !USE_IPHONE
 }
 
@@ -2120,7 +2521,25 @@ last_child (NSView *parent)
 
 # else // USE_IPHONE
 
-  // Controls is an array of arrays of the controls, divided into sections.
+  /* Controls is an array of arrays of the controls, divided into sections.
+     Each hgroup / vgroup gets a nested array, too, e.g.:
+
+       [ [ [ <label>, <checkbox> ],
+           [ <label>, <checkbox> ],
+           [ <label>, <checkbox> ] ],
+         [ <label>, <text-field> ],
+         [ <label>, <low-label>, <slider>, <high-label> ],
+         [ <low-label>, <slider>, <high-label> ],
+         <HTML-label>
+       ];
+
+     If an element begins with a label, it is terminal, otherwise it is a
+     group.  There are (currently) never more than 4 elements in a single
+     terminal element.
+
+     A blank vertical spacer is placed between each hgroup / vgroup,
+     by making each of those a new section in the TableView.
+   */
   if (! controls)
     controls = [[NSMutableArray arrayWithCapacity:10] retain];
   if ([controls count] == 0)
@@ -2138,7 +2557,7 @@ last_child (NSView *parent)
       NSAssert ([(NSArray *) old count] < 4, @"internal error");
       [(NSMutableArray *) old addObject: child];
     } else {
-      // Replace the control in this cell with an array, then app
+      // Replace the control in this cell with an array, then append
       NSMutableArray *a = [NSMutableArray arrayWithObjects: old, child, nil];
       [current replaceObjectAtIndex:[current count]-1 withObject:a];
     }
@@ -2281,6 +2700,9 @@ layout_group (NSView *group, BOOL horiz_p)
   } else if ([name isEqualToString:@"command"]) {
     // do nothing: this is the "-root" business
 
+  } else if ([name isEqualToString:@"video"]) {
+    // ignored
+
   } else if ([name isEqualToString:@"boolean"]) {
     [self makeCheckbox:node on:parent];
 
@@ -2306,6 +2728,9 @@ layout_group (NSView *group, BOOL horiz_p)
   } else if ([name isEqualToString:@"xscreensaver-image"]) {
     [self makeImageLoaderControlBox:node on:parent];
 
+  } else if ([name isEqualToString:@"xscreensaver-updater"]) {
+    [self makeUpdaterControlBox:node on:parent];
+
   } else {
     NSAssert1 (0, @"unknown tag: %@", name);
   }
@@ -2317,7 +2742,7 @@ layout_group (NSView *group, BOOL horiz_p)
 - (void)traverseChildren:(NSXMLNode *)node on:(NSView *)parent
 {
   NSArray *children = [node children];
-  int i, count = [children count];
+  NSUInteger i, count = [children count];
   for (i = 0; i < count; i++) {
     NSXMLNode *child = [children objectAtIndex:i];
     [self makeControl:child on:parent];
@@ -2437,14 +2862,14 @@ fix_contentview_size (NSView *parent)
   }
   
 /*
-Bad:
- parent: 420 x 541 @   0   0
- text:   380 x 100 @  20  22  miny=-501
+    Bad:
    parent: 420 x 541 @   0   0
    text:   380 x 100 @  20  22  miny=-501
 
-Good:
- parent: 420 x 541 @   0   0
- text:   380 x 100 @  20  50  miny=-501
-*/
+    Good:
    parent: 420 x 541 @   0   0
    text:   380 x 100 @  20  50  miny=-501
+ */
 
   // #### WTF2: See "WTF" above.  If the text field is off the screen,
   //      move it up.  We need this on 10.6 but not on 10.5.  Auugh.
@@ -2578,6 +3003,8 @@ wrap_with_buttons (NSWindow *window, NSView *panel)
   [cancel setAction:@selector(cancelAction:)];
   [reset  setAction:@selector(resetAction:)];
   
+  [bbox release];
+
   return pbox;
 }
 #endif // !USE_IPHONE
@@ -2599,6 +3026,8 @@ wrap_with_buttons (NSWindow *window, NSView *panel)
   }
 
   saver_name = [self parseXScreenSaverTag: node];
+  saver_name = [saver_name stringByReplacingOccurrencesOfString:@" "
+                           withString:@""];
   [saver_name retain];
   
 # ifndef USE_IPHONE
@@ -2612,7 +3041,8 @@ wrap_with_buttons (NSWindow *window, NSView *panel)
   fix_contentview_size (panel);
 
   NSView *root = wrap_with_buttons (parent, panel);
-  [userDefaultsController setAppliesImmediately:NO];
+  [userDefaultsController   setAppliesImmediately:NO];
+  [globalDefaultsController setAppliesImmediately:NO];
 
   [panel setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
 
@@ -2621,6 +3051,9 @@ wrap_with_buttons (NSWindow *window, NSView *panel)
   [parent setMinSize:rect.size];
   
   [parent setContentView:root];
+       
+  [panel release];
+  [root release];
 
 # else  // USE_IPHONE
 
@@ -2698,7 +3131,7 @@ wrap_with_buttons (NSWindow *window, NSView *panel)
 - (CGFloat)pickerView:(UIPickerView *)pv
            rowHeightForComponent:(NSInteger)column
 {
-  return [NSFont systemFontSize] * 1.5; // #### WHAT
+  return FONT_SIZE;
 }
 
 - (CGFloat)pickerView:(UIPickerView *)pv
@@ -2753,6 +3186,10 @@ wrap_with_buttons (NSWindow *window, NSView *panel)
                              style: UIBarButtonItemStyleBordered
                              target:self
                              action:@selector(resetAction:)]];
+  NSString *s = saver_name;
+  if ([self view].frame.size.width > 320)
+    s = [s stringByAppendingString: @" Settings"];
+  [self navigationItem].title = s;
 }
 
 
@@ -2778,8 +3215,8 @@ wrap_with_buttons (NSWindow *window, NSView *panel)
               titleForHeaderInSection:(NSInteger)section
 {
   // Titles above each vertically-stacked white box.
-  if (section == 0)
-    return [saver_name stringByAppendingString:@" Settings"];
+//  if (section == 0)
+//    return [saver_name stringByAppendingString:@" Settings"];
   return nil;
 }
 
@@ -2787,7 +3224,7 @@ wrap_with_buttons (NSWindow *window, NSView *panel)
 - (CGFloat)tableView:(UITableView *)tv
            heightForRowAtIndexPath:(NSIndexPath *)ip
 {
-  CGFloat h = [tv rowHeight];
+  CGFloat h = 0;
 
   NSView *ctl = [[controls objectAtIndex:[ip indexAtPosition:0]]
                   objectAtIndex:[ip indexAtPosition:1]];
@@ -2795,32 +3232,36 @@ wrap_with_buttons (NSWindow *window, NSView *panel)
   if ([ctl isKindOfClass:[NSArray class]]) {
     NSArray *set = (NSArray *) ctl;
     switch ([set count]) {
-    case 4:
-# ifdef LABEL_ABOVE_SLIDER
-      h *= 1.7; break; // label + left/slider/right: 2 1/2 lines
-# endif
-    case 3: h *= 1.2; break;   // left/slider/right: 1 1/2 lines
-    case 2:
-      if ([[set objectAtIndex:1] isKindOfClass:[UITextField class]])
-        h *= 1.2;
+    case 4:                    // label + left/slider/right.
+    case 3:                    // left/slider/right.
+      h = FONT_SIZE * 3.0;
+      break;
+    case 2:                    // Checkboxes, or text fields.
+      h = FONT_SIZE * 2.4;
       break;
     }
   } else if ([ctl isKindOfClass:[UILabel class]]) {
-    UILabel *t = (UILabel *) ctl;
+    // Radio buttons in a multi-select list.
+    h = FONT_SIZE * 1.9;
+
+# ifdef USE_HTML_LABELS
+  } else if ([ctl isKindOfClass:[HTMLLabel class]]) {
+    
+    HTMLLabel *t = (HTMLLabel *) ctl;
     CGRect r = t.frame;
-    r.size.width = 250;                // WTF! Black magic!
-    r.size.width -= LEFT_MARGIN;
+    r.size.width = [tv frame].size.width;
+    r.size.width -= LEFT_MARGIN * 2;
     [t setFrame:r];
     [t sizeToFit];
     r = t.frame;
-    h = r.size.height + LINE_SPACING * 3;
+    h = r.size.height;
+# endif // USE_HTML_LABELS
 
-  } else {
-    CGFloat h2 = [ctl frame].size.height;
-    h2 += LINE_SPACING * 2;
-    if (h2 > h) h = h2;
+  } else {                     // Does this ever happen?
+    h = FONT_SIZE + LINE_SPACING * 2;
   }
 
+  if (h <= 0) abort();
   return h;
 }
 
@@ -2829,9 +3270,9 @@ wrap_with_buttons (NSWindow *window, NSView *panel)
 {
   UITableView *tv = (UITableView *) [self view];
   NSMutableArray *a = [NSMutableArray arrayWithCapacity:20];
-  int rows = [self numberOfSectionsInTableView:tv];
+  NSInteger rows = [self numberOfSectionsInTableView:tv];
   for (int i = 0; i < rows; i++) {
-    int cols = [self tableView:tv numberOfRowsInSection:i];
+    NSInteger cols = [self tableView:tv numberOfRowsInSection:i];
     for (int j = 0; j < cols; j++) {
       NSUInteger ip[2];
       ip[0] = i;
@@ -2843,6 +3284,11 @@ wrap_with_buttons (NSWindow *window, NSView *panel)
   [tv beginUpdates];
   [tv reloadRowsAtIndexPaths:a withRowAnimation:UITableViewRowAnimationNone];
   [tv endUpdates];
+
+  // Default opacity looks bad.
+  // #### Oh great, this only works *sometimes*.
+  UIView *v = [[self navigationItem] titleView];
+  [v setBackgroundColor:[[v backgroundColor] colorWithAlphaComponent:1]];
 }
 
 
@@ -2864,7 +3310,8 @@ wrap_with_buttons (NSWindow *window, NSView *panel)
   NSArray *item = [[b items] objectAtIndex: [b index]];
   NSString *pref_key = [item objectAtIndex:1];
   NSObject *pref_val = [item objectAtIndex:2];
-  NSObject *current = [userDefaultsController objectForKey:pref_key];
+
+  NSObject *current = [[self controllerForKey:pref_key] objectForKey:pref_key];
 
   // Convert them both to strings and compare those, so that
   // we don't get screwed by int 1 versus string "1".
@@ -2907,46 +3354,23 @@ wrap_with_buttons (NSWindow *window, NSView *panel)
 - (UITableViewCell *)tableView:(UITableView *)tv
                      cellForRowAtIndexPath:(NSIndexPath *)ip
 {
-#if 0
-  /* #### If we re-use cells, then clicking on a checkbox RadioButton
-          (in non-USE_PICKER_VIEW mode) makes all the cells disappear.
-          This doesn't happen if we don't re-use any cells. Oh well.
-   */
-  NSString *id = [NSString stringWithFormat: @"%d:%d",
-                           [ip indexAtPosition:0],
-                           [ip indexAtPosition:1]];
-  UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier: id];
-
-  if (cell) return cell;
-#else
-  NSString *id = nil;
-  UITableViewCell *cell;
-#endif
-
-  cell = [[[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault
-                                   reuseIdentifier: id]
-           autorelease];
-  cell.selectionStyle = UITableViewCellSelectionStyleNone;
+  CGFloat ww = [tv frame].size.width;
+  CGFloat hh = [self tableView:tv heightForRowAtIndexPath:ip];
 
-  CGRect p = [cell frame];
-  CGRect r;
+  float os_version = [[[UIDevice currentDevice] systemVersion] floatValue];
 
-  p.size.height = [self tableView:tv heightForRowAtIndexPath:ip];
-  [cell setFrame:p];
+  // Width of the column of labels on the left.
+  CGFloat left_width = ww * 0.4;
+  CGFloat right_edge = ww - LEFT_MARGIN;
 
-  // Allocate more space to the controls on iPad screens,
-  // and on landscape-mode iPhones.
-  CGFloat ww = [tv frame].size.width;
-  CGFloat left_edge = (ww > 700
-                       ? p.size.width * 0.9
-                       : ww > 320
-                       ? p.size.width * 0.5
-                       : p.size.width * 0.3);
-  CGFloat right_edge = p.origin.x + p.size.width - LEFT_MARGIN;
+  if (os_version < 7)  // margins were wider on iOS 6.1
+    right_edge -= 10;
 
+  CGFloat max = FONT_SIZE * 12;
+  if (left_width > max) left_width = max;
 
   NSView *ctl = [[controls objectAtIndex:[ip indexAtPosition:0]]
-                  objectAtIndex:[ip indexAtPosition:1]];
+                           objectAtIndex:[ip indexAtPosition:1]];
 
   if ([ctl isKindOfClass:[NSArray class]]) {
     // This cell has a set of objects in it.
@@ -2955,32 +3379,53 @@ wrap_with_buttons (NSWindow *window, NSView *panel)
     case 2:
       {
         // With 2 elements, the first of the pair must be a label.
-        ctl = [set objectAtIndex: 0];
-        NSAssert ([ctl isKindOfClass:[UILabel class]], @"unhandled type");
-        cell.textLabel.text = [(UILabel *) ctl text];
+        UILabel *label = (UILabel *) [set objectAtIndex: 0];
+        NSAssert ([label isKindOfClass:[UILabel class]], @"unhandled type");
         ctl = [set objectAtIndex: 1];
 
-        r = [ctl frame];
-        if ([ctl isKindOfClass:[UISwitch class]]) {
-          // Flush right checkboxes.
-          ctl.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
+        CGRect r = [ctl frame];
+
+        if ([ctl isKindOfClass:[UISwitch class]]) {    // Checkboxes.
           r.size.width = 80;  // Magic.
-          r.origin.x = right_edge - r.size.width;
+          r.origin.x = right_edge - r.size.width + 30;  // beats me
+
+          if (os_version < 7)  // checkboxes were wider on iOS 6.1
+            r.origin.x -= 25;
+
         } else {
-          // Expandable sliders.
-          ctl.autoresizingMask = UIViewAutoresizingFlexibleWidth;
-          r.origin.x = left_edge;
+          r.origin.x = left_width;                     // Text fields, etc.
           r.size.width = right_edge - r.origin.x;
         }
-        r.origin.y = (p.size.height - r.size.height) / 2;
+
+        r.origin.y = (hh - r.size.height) / 2;   // Center vertically.
         [ctl setFrame:r];
+
+        // Make a box and put the label and checkbox/slider into it.
+        r.origin.x = 0;
+        r.origin.y = 0;
+        r.size.width  = ww;
+        r.size.height = hh;
+        NSView *box = [[UIView alloc] initWithFrame:r];
+        [box addSubview: ctl];
+
+        // Let the label make use of any space not taken up by the control.
+        r = [label frame];
+        r.origin.x = LEFT_MARGIN;
+        r.origin.y = 0;
+        r.size.width  = [ctl frame].origin.x - r.origin.x;
+        r.size.height = hh;
+        [label setFrame:r];
+        [label setFont:[NSFont boldSystemFontOfSize: FONT_SIZE]];
+        [box addSubview: label];
+
+        ctl = box;
       }
       break;
     case 3:
     case 4:
       {
-        // With 3 elements, the first and last must be labels.
-        // With 4 elements, the first, second and last must be labels.
+        // With 3 elements, 1 and 3 are labels.
+        // With 4 elements, 1, 2 and 4 are labels.
         int i = 0;
         UILabel *top  = ([set count] == 4
                          ? [set objectAtIndex: i++]
@@ -2995,94 +3440,106 @@ wrap_with_buttons (NSWindow *window, NSView *panel)
 
         // 3 elements: control at top of cell.
         // 4 elements: center the control vertically.
-        r = [mid frame];
+        CGRect r = [mid frame];
+        r.size.height = 32;   // Unchangable height of the slider thumb.
+
+        // Center the slider between left_width and right_edge.
 # ifdef  LABEL_ABOVE_SLIDER
-        left_edge = LEFT_MARGIN;
+        r.origin.x = LEFT_MARGIN;
+# else
+        r.origin.x = left_width;
 # endif
-        r.origin.x = left_edge;
+        r.origin.y = (hh - r.size.height) / 2;
         r.size.width = right_edge - r.origin.x;
-        r.origin.y = ([set count] == 3
-                      ? 8
-                      : (p.size.height - r.size.height) / 2);
         [mid setFrame:r];
 
-        // Top label goes above, flush center/top.
         if (top) {
-          // [top setFont:[[cell textLabel] font]];  // 0 point?
           r.size = [[top text] sizeWithFont:[top font]
                                constrainedToSize:
-                                 CGSizeMake (p.size.width - LEFT_MARGIN*2,
-                                             100000)
+                                 CGSizeMake (ww - LEFT_MARGIN*2, 100000)
                                lineBreakMode:[top lineBreakMode]];
-          r.origin.x = (p.size.width - r.size.width) / 2;
+# ifdef LABEL_ABOVE_SLIDER
+          // Top label goes above, flush center/top.
+          r.origin.x = (ww - r.size.width) / 2;
           r.origin.y = 4;
+# else  // !LABEL_ABOVE_SLIDER
+          // Label goes on the left.
+          r.origin.x = LEFT_MARGIN;
+          r.origin.y = 0;
+          r.size.width  = left_width - LEFT_MARGIN;
+          r.size.height = hh;
+# endif // !LABEL_ABOVE_SLIDER
           [top setFrame:r];
         }
 
         // Left label goes under control, flush left/bottom.
         r.size = [[left text] sizeWithFont:[left font]
-                               constrainedToSize:
-                                 CGSizeMake(p.size.width - LEFT_MARGIN*2,
-                                            100000)
+                              constrainedToSize:
+                                CGSizeMake(ww - LEFT_MARGIN*2, 100000)
                               lineBreakMode:[left lineBreakMode]];
         r.origin.x = [mid frame].origin.x;
-        r.origin.y = p.size.height - r.size.height - 4;
+        r.origin.y = hh - r.size.height - 4;
         [left setFrame:r];
 
         // Right label goes under control, flush right/bottom.
         r = [right frame];
         r.size = [[right text] sizeWithFont:[right font]
                                constrainedToSize:
-                                 CGSizeMake(p.size.width - LEFT_MARGIN*2,
-                                            1000000)
+                                 CGSizeMake(ww - LEFT_MARGIN*2, 1000000)
                                lineBreakMode:[right lineBreakMode]];
         r.origin.x = ([mid frame].origin.x + [mid frame].size.width -
                       r.size.width);
         r.origin.y = [left frame].origin.y;
         [right setFrame:r];
 
-        // Then make a box.
-        ctl = [[UIView alloc] initWithFrame:p];
-        if (top) {
-# ifdef LABEL_ABOVE_SLIDER
-          [ctl addSubview: top];
-          top.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin|
-                                  UIViewAutoresizingFlexibleRightMargin);
-# else
-          cell.textLabel.text = [top text];
-# endif
-        }
-        [ctl addSubview: left];
-        [ctl addSubview: mid];
-        [ctl addSubview: right];
-
-        left. autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
-        mid.  autoresizingMask = UIViewAutoresizingFlexibleWidth;
-        right.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
-        ctl.  autoresizingMask = UIViewAutoresizingFlexibleWidth;
+        // Make a box and put the labels and slider into it.
+        r.origin.x = 0;
+        r.origin.y = 0;
+        r.size.width  = ww;
+        r.size.height = hh;
+        NSView *box = [[UIView alloc] initWithFrame:r];
+        if (top)
+          [box addSubview: top];
+        [box addSubview: left];
+        [box addSubview: right];
+        [box addSubview: mid];
+
+        ctl = box;
       }
       break;
     default:
       NSAssert (0, @"unhandled size");
     }
-  } else {
-    // A single view, not a pair.
-
-    r = [ctl frame];
+  } else {     // A single view, not a pair.
+    CGRect r = [ctl frame];
     r.origin.x = LEFT_MARGIN;
-    r.origin.y = LINE_SPACING;
+    r.origin.y = 0;
     r.size.width = right_edge - r.origin.x;
+    r.size.height = hh;
     [ctl setFrame:r];
+  }
 
-    ctl.autoresizingMask = UIViewAutoresizingFlexibleWidth;
+  NSString *id = @"Cell";
+  UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:id];
+  if (!cell)
+    cell = [[[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault
+                                     reuseIdentifier: id]
+             autorelease];
+
+  for (UIView *subview in [cell.contentView subviews])
+    [subview removeFromSuperview];
+  [cell.contentView addSubview: ctl];
+  CGRect r = [ctl frame];
+  r.origin.x = 0;
+  r.origin.y = 0;
+  [cell setFrame:r];
+  cell.selectionStyle = UITableViewCellSelectionStyleNone;
+  [cell setAccessoryType:UITableViewCellAccessoryNone];
 
 # ifndef USE_PICKER_VIEW
-    if ([ctl isKindOfClass:[RadioButton class]])
-      [self updateRadioGroupCell:cell button:(RadioButton *)ctl];
+  if ([ctl isKindOfClass:[RadioButton class]])
+    [self updateRadioGroupCell:cell button:(RadioButton *)ctl];
 # endif // USE_PICKER_VIEW
-  }
-
-  [cell.contentView addSubview: ctl];
 
   return cell;
 }
@@ -3093,10 +3550,11 @@ wrap_with_buttons (NSWindow *window, NSView *panel)
    controls on itself that are hooked up to the appropriate preferences.
    The default size of the view is just big enough to hold them all.
  */
-- (id)initWithXMLFile: (NSString *) xml_file
-              options: (const XrmOptionDescRec *) _opts
-           controller: (NSUserDefaultsController *) _prefs
-             defaults: (NSDictionary *) _defs
+- (id)initWithXML: (NSData *) xml_data
+          options: (const XrmOptionDescRec *) _opts
+       controller: (NSUserDefaultsController *) _prefs
+ globalController: (NSUserDefaultsController *) _globalPrefs
+         defaults: (NSDictionary *) _defs
 {
 # ifndef USE_IPHONE
   self = [super init];
@@ -3109,43 +3567,24 @@ wrap_with_buttons (NSWindow *window, NSView *panel)
   // instance variables
   opts = _opts;
   defaultOptions = _defs;
-  userDefaultsController = _prefs;
-  [userDefaultsController retain];
-
-  NSURL *furl = [NSURL fileURLWithPath:xml_file];
-
-  if (!furl) {
-    NSAssert1 (0, @"can't URLify \"%@\"", xml_file);
-    return nil;
-  }
-
-#if 0  // -- the old way
-  NSError *err = nil;
-  NSXMLDocument *xmlDoc = [[NSXMLDocument alloc] 
-                            initWithContentsOfURL:furl
-                            options:(NSXMLNodePreserveWhitespace |
-                                     NSXMLNodePreserveCDATA)
-                            error:&err];
-  if (!xmlDoc || err) {
-    if (err)
-      NSAssert2 (0, @"XML Error: %@: %@",
-                 xml_file, [err localizedDescription]);
-    return nil;
-  }
-
-  traverse_tree (prefs, self, opts, [xmlDoc rootElement]);
-#endif /* 0 */
+  userDefaultsController   = [_prefs retain];
+  globalDefaultsController = [_globalPrefs retain];
 
+  NSXMLParser *xmlDoc = [[NSXMLParser alloc] initWithData:xml_data];
 
-  NSXMLParser *xmlDoc = [[NSXMLParser alloc] initWithContentsOfURL:furl];
   if (!xmlDoc) {
-    NSAssert1 (0, @"XML Error: %@", xml_file);
+    NSAssert1 (0, @"XML Error: %@",
+               [[NSString alloc] initWithData:xml_data
+                                 encoding:NSUTF8StringEncoding]);
     return nil;
   }
   [xmlDoc setDelegate:self];
   if (! [xmlDoc parse]) {
     NSError *err = [xmlDoc parserError];
-    NSAssert2 (0, @"XML Error: %@: %@", xml_file, err);
+    NSAssert2 (0, @"XML Error: %@: %@",
+               [[NSString alloc] initWithData:xml_data
+                                 encoding:NSUTF8StringEncoding],
+               err);
     return nil;
   }
 
@@ -3164,6 +3603,7 @@ wrap_with_buttons (NSWindow *window, NSView *panel)
 {
   [saver_name release];
   [userDefaultsController release];
+  [globalDefaultsController release];
 # ifdef USE_IPHONE
   [controls release];
   [pref_keys release];