+ [parent setContentView:root];
+
+# else // USE_IPHONE
+
+ CGRect r = [parent frame];
+ r.size = [[UIScreen mainScreen] bounds].size;
+ [parent setFrame:r];
+ [self traverseChildren:node on:parent];
+
+# endif // USE_IPHONE
+}
+
+
+- (void)parser:(NSXMLParser *)parser
+ didStartElement:(NSString *)elt
+ namespaceURI:(NSString *)ns
+ qualifiedName:(NSString *)qn
+ attributes:(NSDictionary *)attrs
+{
+ NSXMLElement *e = [[NSXMLElement alloc] initWithName:elt];
+ [e setKind:SimpleXMLElementKind];
+ [e setAttributesAsDictionary:attrs];
+ NSXMLElement *p = xml_parsing;
+ [e setParent:p];
+ xml_parsing = e;
+ if (! xml_root)
+ xml_root = xml_parsing;
+}
+
+- (void)parser:(NSXMLParser *)parser
+ didEndElement:(NSString *)elt
+ namespaceURI:(NSString *)ns
+ qualifiedName:(NSString *)qn
+{
+ NSXMLElement *p = xml_parsing;
+ if (! p) {
+ NSLog(@"extra close: %@", elt);
+ } else if (![[p name] isEqualToString:elt]) {
+ NSLog(@"%@ closed by %@", [p name], elt);
+ } else {
+ NSXMLElement *n = xml_parsing;
+ xml_parsing = [n parent];
+ }
+}
+
+
+- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
+{
+ NSXMLElement *e = [[NSXMLElement alloc] initWithName:@"text"];
+ [e setKind:SimpleXMLTextKind];
+ NSXMLElement *p = xml_parsing;
+ [e setParent:p];
+ [e setObjectValue: string];
+}
+
+
+# ifdef USE_IPHONE
+# ifdef USE_PICKER_VIEW
+
+#pragma mark UIPickerView delegate methods
+
+- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pv
+{
+ return 1; // Columns
+}
+
+- (NSInteger)pickerView:(UIPickerView *)pv
+ numberOfRowsInComponent:(NSInteger)column
+{
+ NSAssert (column == 0, @"weird column");
+ NSArray *a = [picker_values objectAtIndex: [pv tag]];
+ if (! a) return 0; // Too early?
+ return [a count];
+}
+
+- (CGFloat)pickerView:(UIPickerView *)pv
+ rowHeightForComponent:(NSInteger)column
+{
+ return FONT_SIZE;
+}
+
+- (CGFloat)pickerView:(UIPickerView *)pv
+ widthForComponent:(NSInteger)column
+{
+ NSAssert (column == 0, @"weird column");
+ NSArray *a = [picker_values objectAtIndex: [pv tag]];
+ if (! a) return 0; // Too early?
+
+ UIFont *f = [UIFont systemFontOfSize:[NSFont systemFontSize]];
+ CGFloat max = 0;
+ for (NSArray *a2 in a) {
+ NSString *s = [a2 objectAtIndex:0];
+ CGSize r = [s sizeWithFont:f];
+ if (r.width > max) max = r.width;
+ }
+
+ max *= 1.7; // WTF!!
+
+ if (max > 320)
+ max = 320;
+ else if (max < 120)
+ max = 120;
+
+ return max;
+
+}
+
+
+- (NSString *)pickerView:(UIPickerView *)pv
+ titleForRow:(NSInteger)row
+ forComponent:(NSInteger)column
+{
+ NSAssert (column == 0, @"weird column");
+ NSArray *a = [picker_values objectAtIndex: [pv tag]];
+ if (! a) return 0; // Too early?
+ a = [a objectAtIndex:row];
+ NSAssert (a, @"internal error");
+ return [a objectAtIndex:0];
+}
+
+# endif // USE_PICKER_VIEW
+
+
+#pragma mark UITableView delegate methods
+
+- (void) addResetButton
+{
+ [[self navigationItem]
+ setRightBarButtonItem: [[UIBarButtonItem alloc]
+ initWithTitle: @"Reset to Defaults"
+ 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;
+}
+
+
+- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)o
+{
+ return YES;
+}
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tv {
+ // Number of vertically-stacked white boxes.
+ return [controls count];
+}
+
+- (NSInteger)tableView:(UITableView *)tableView
+ numberOfRowsInSection:(NSInteger)section
+{
+ // Number of lines in each vertically-stacked white box.
+ NSAssert (controls, @"internal error");
+ return [[controls objectAtIndex:section] count];
+}
+
+- (NSString *)tableView:(UITableView *)tv
+ titleForHeaderInSection:(NSInteger)section
+{
+ // Titles above each vertically-stacked white box.
+// if (section == 0)
+// return [saver_name stringByAppendingString:@" Settings"];
+ return nil;
+}
+
+
+- (CGFloat)tableView:(UITableView *)tv
+ heightForRowAtIndexPath:(NSIndexPath *)ip
+{
+ CGFloat h = [tv rowHeight];
+
+ NSView *ctl = [[controls objectAtIndex:[ip indexAtPosition:0]]
+ objectAtIndex:[ip indexAtPosition:1]];
+
+ 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;
+ break;
+ }
+ } else if ([ctl isKindOfClass:[UILabel class]]) {
+ UILabel *t = (UILabel *) ctl;
+ CGRect r = t.frame;
+ r.size.width = 250; // WTF! Black magic!
+ r.size.width -= LEFT_MARGIN;
+ [t setFrame:r];
+ [t sizeToFit];
+ r = t.frame;
+ h = r.size.height + LINE_SPACING * 3;
+# ifdef USE_HTML_LABELS
+
+ } else if ([ctl isKindOfClass:[HTMLLabel class]]) {
+
+ HTMLLabel *t = (HTMLLabel *) ctl;
+ CGRect r = t.frame;
+ 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;
+
+# endif // USE_HTML_LABELS
+ } else {
+ CGFloat h2 = [ctl frame].size.height;
+ h2 += LINE_SPACING * 2;
+ if (h2 > h) h = h2;
+ }
+
+ return h;
+}
+
+
+- (void)refreshTableView
+{
+ UITableView *tv = (UITableView *) [self view];
+ NSMutableArray *a = [NSMutableArray arrayWithCapacity:20];
+ int rows = [self numberOfSectionsInTableView:tv];
+ for (int i = 0; i < rows; i++) {
+ int cols = [self tableView:tv numberOfRowsInSection:i];
+ for (int j = 0; j < cols; j++) {
+ NSUInteger ip[2];
+ ip[0] = i;
+ ip[1] = j;
+ [a addObject: [NSIndexPath indexPathWithIndexes:ip length:2]];
+ }
+ }
+
+ [tv beginUpdates];
+ [tv reloadRowsAtIndexPaths:a withRowAnimation:UITableViewRowAnimationNone];
+ [tv endUpdates];
+}
+
+
+- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)o
+{
+ [NSTimer scheduledTimerWithTimeInterval: 0
+ target:self
+ selector:@selector(refreshTableView)
+ userInfo:nil
+ repeats:NO];
+}
+
+
+#ifndef USE_PICKER_VIEW
+
+- (void)updateRadioGroupCell:(UITableViewCell *)cell
+ button:(RadioButton *)b
+{
+ NSArray *item = [[b items] objectAtIndex: [b index]];
+ NSString *pref_key = [item objectAtIndex:1];
+ NSObject *pref_val = [item objectAtIndex:2];
+
+ 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".
+ // Will boolean true/1 screw us here too?
+ //
+ NSString *pref_str = ([pref_val isKindOfClass:[NSString class]]
+ ? (NSString *) pref_val
+ : [(NSNumber *) pref_val stringValue]);
+ NSString *current_str = ([current isKindOfClass:[NSString class]]
+ ? (NSString *) current
+ : [(NSNumber *) current stringValue]);
+ BOOL match_p = [current_str isEqualToString:pref_str];
+
+ // NSLog(@"\"%@\" = \"%@\" | \"%@\" ", pref_key, pref_val, current_str);
+
+ if (match_p)
+ [cell setAccessoryType:UITableViewCellAccessoryCheckmark];
+ else
+ [cell setAccessoryType:UITableViewCellAccessoryNone];
+}
+
+
+- (void)tableView:(UITableView *)tv
+ didSelectRowAtIndexPath:(NSIndexPath *)ip
+{
+ RadioButton *ctl = [[controls objectAtIndex:[ip indexAtPosition:0]]
+ objectAtIndex:[ip indexAtPosition:1]];
+ if (! [ctl isKindOfClass:[RadioButton class]])
+ return;
+
+ [self radioAction:ctl];
+ [self refreshTableView];
+}
+
+
+#endif // !USE_PICKER_VIEW
+
+
+
+- (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;
+
+ CGRect p = [cell frame];
+ CGRect r;
+
+ p.size.height = [self tableView:tv heightForRowAtIndexPath:ip];
+ [cell setFrame:p];
+
+ // 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;
+
+
+ NSView *ctl = [[controls objectAtIndex:[ip indexAtPosition:0]]
+ objectAtIndex:[ip indexAtPosition:1]];
+
+ if ([ctl isKindOfClass:[NSArray class]]) {
+ // This cell has a set of objects in it.
+ NSArray *set = (NSArray *) ctl;
+ switch ([set count]) {
+ case 2:
+ {
+ // With 2 elements, the first of the pair must be a label.
+ 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;
+ r.size.width = 80; // Magic.
+ r.origin.x = right_edge - r.size.width;
+ } else {
+ // Expandable sliders.
+ ctl.autoresizingMask = UIViewAutoresizingFlexibleWidth;
+ r.origin.x = left_edge;
+ r.size.width = right_edge - r.origin.x;
+ }
+ r.origin.y = (p.size.height - r.size.height) / 2;
+ [ctl setFrame:r];
+
+ // Make a box.
+ NSView *box = [[UIView alloc] initWithFrame:p];
+ [box addSubview: ctl];
+
+ // cell.textLabel.text = [(UILabel *) ctl text];
+ 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 = p.size.height;
+ [label setFrame:r];
+ [label setFont:[NSFont boldSystemFontOfSize: FONT_SIZE]];
+ label.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
+ box. autoresizingMask = UIViewAutoresizingFlexibleWidth;
+ [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.
+ int i = 0;
+ UILabel *top = ([set count] == 4
+ ? [set objectAtIndex: i++]
+ : 0);
+ UILabel *left = [set objectAtIndex: i++];
+ NSView *mid = [set objectAtIndex: i++];
+ UILabel *right = [set objectAtIndex: i++];
+ NSAssert (!top || [top isKindOfClass:[UILabel class]], @"WTF");
+ NSAssert ( [left isKindOfClass:[UILabel class]], @"WTF");
+ NSAssert ( ![mid isKindOfClass:[UILabel class]], @"WTF");
+ NSAssert ( [right isKindOfClass:[UILabel class]], @"WTF");
+
+ // 3 elements: control at top of cell.
+ // 4 elements: center the control vertically.
+ r = [mid frame];
+# ifdef LABEL_ABOVE_SLIDER
+ left_edge = LEFT_MARGIN;
+# endif
+ r.origin.x = left_edge;
+ 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) {
+ r.size = [[top text] sizeWithFont:[top font]
+ constrainedToSize:
+ CGSizeMake (p.size.width - LEFT_MARGIN*2,
+ 100000)
+ lineBreakMode:[top lineBreakMode]];
+ r.origin.x = (p.size.width - r.size.width) / 2;
+ r.origin.y = 4;
+ [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)
+ lineBreakMode:[left lineBreakMode]];
+ r.origin.x = [mid frame].origin.x;
+ r.origin.y = p.size.height - 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)
+ 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
+ r = [top frame];
+ r.origin.x = LEFT_MARGIN;
+ r.origin.y = 0;
+ r.size.width = [mid frame].origin.x - r.origin.x;
+ r.size.height = p.size.height;
+ [top setFrame:r];
+ top.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
+ [ctl addSubview: top];
+# endif
+ }
+ [ctl addSubview: left];
+ [ctl addSubview: mid];
+ [ctl addSubview: right];
+
+ left. autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
+ mid. autoresizingMask = UIViewAutoresizingFlexibleWidth;
+ right.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
+ ctl. autoresizingMask = UIViewAutoresizingFlexibleWidth;
+ }
+ break;
+ default:
+ NSAssert (0, @"unhandled size");
+ }
+ } else {
+ // A single view, not a pair.
+
+ r = [ctl frame];
+ r.origin.x = LEFT_MARGIN;
+ r.origin.y = LINE_SPACING;
+ r.size.width = right_edge - r.origin.x;
+ [ctl setFrame:r];
+
+ ctl.autoresizingMask = UIViewAutoresizingFlexibleWidth;
+
+# ifndef USE_PICKER_VIEW
+ if ([ctl isKindOfClass:[RadioButton class]])
+ [self updateRadioGroupCell:cell button:(RadioButton *)ctl];
+# endif // USE_PICKER_VIEW
+ }
+
+ if ([ctl isKindOfClass:[UILabel class]]) {
+ // Make label full height to allow text to line-wrap if necessary.
+ r = [ctl frame];
+ r.origin.y = p.origin.y;
+ r.size.height = p.size.height;
+ [ctl setFrame:r];
+ }
+
+ [cell.contentView addSubview: ctl];
+
+ return cell;