1 /* xscreensaver, Copyright (c) 2006-2013 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
12 /* XScreenSaver uses XML files to describe the user interface for configuring
13 the various screen savers. These files live in .../hacks/config/ and
14 say relatively high level things like: "there should be a checkbox
15 labelled "Leave Trails", and when it is checked, add the option '-trails'
16 to the command line when launching the program."
18 This code reads that XML and constructs a Cocoa interface from it.
19 The Cocoa controls are hooked up to NSUserDefaultsController to save
20 those settings into the MacOS preferences system. The Cocoa preferences
21 names are the same as the resource names specified in the screenhack's
22 'options' array (we use that array to map the command line switches
23 specified in the XML to the resource names to use).
26 #import "XScreenSaverConfigSheet.h"
29 #import "InvertedSlider.h"
32 # define NSView UIView
33 # define NSRect CGRect
34 # define NSSize CGSize
35 # define NSTextField UITextField
36 # define NSButton UIButton
37 # define NSFont UIFont
38 # define NSStepper UIStepper
39 # define NSMenuItem UIMenuItem
40 # define NSText UILabel
41 # define minValue minimumValue
42 # define maxValue maximumValue
43 # define setMinValue setMinimumValue
44 # define setMaxValue setMaximumValue
45 # define LABEL UILabel
47 # define LABEL NSTextField
50 #undef LABEL_ABOVE_SLIDER
51 #define USE_HTML_LABELS
54 #pragma mark XML Parser
56 /* I used to use the "NSXMLDocument" XML parser, but that doesn't exist
57 on iOS. The "NSXMLParser" parser exists on both OSX and iOS, so I
58 converted to use that. However, to avoid having to re-write all of
59 the old code, I faked out a halfassed implementation of the
60 "NSXMLNode" class that "NSXMLDocument" used to return.
63 #define NSXMLNode SimpleXMLNode
64 #define NSXMLElement SimpleXMLNode
65 #define NSXMLCommentKind SimpleXMLCommentKind
66 #define NSXMLElementKind SimpleXMLElementKind
67 #define NSXMLAttributeKind SimpleXMLAttributeKind
68 #define NSXMLTextKind SimpleXMLTextKind
70 typedef enum { SimpleXMLCommentKind,
72 SimpleXMLAttributeKind,
76 @interface SimpleXMLNode : NSObject
80 SimpleXMLNode *parent;
81 NSMutableArray *children;
82 NSMutableArray *attributes;
86 @property(nonatomic) SimpleXMLKind kind;
87 @property(nonatomic, retain) NSString *name;
88 @property(nonatomic, retain) SimpleXMLNode *parent;
89 @property(nonatomic, retain) NSMutableArray *children;
90 @property(nonatomic, retain) NSMutableArray *attributes;
91 @property(nonatomic, retain, getter=objectValue, setter=setObjectValue:)
96 @implementation SimpleXMLNode
100 //@synthesize parent;
101 @synthesize children;
102 @synthesize attributes;
108 attributes = [NSMutableArray arrayWithCapacity:10];
113 - (id) initWithName:(NSString *)n
116 [self setKind:NSXMLElementKind];
122 - (void) setAttributesAsDictionary:(NSDictionary *)dict
124 for (NSString *key in dict) {
125 NSObject *val = [dict objectForKey:key];
126 SimpleXMLNode *n = [[SimpleXMLNode alloc] init];
127 [n setKind:SimpleXMLAttributeKind];
129 [n setObjectValue:val];
130 [attributes addObject:n];
134 - (SimpleXMLNode *) parent { return parent; }
136 - (void) setParent:(SimpleXMLNode *)p
138 NSAssert (!parent, @"parent already set");
141 NSMutableArray *kids = [p children];
143 kids = [NSMutableArray arrayWithCapacity:10];
144 [p setChildren:kids];
146 [kids addObject:self];
151 #pragma mark Implementing radio buttons
153 /* The UIPickerView is a hideous and uncustomizable piece of shit.
154 I can't believe Apple actually released that thing on the world.
155 Let's fake up some radio buttons instead.
158 #if defined(USE_IPHONE) && !defined(USE_PICKER_VIEW)
160 @interface RadioButton : UILabel
166 @property(nonatomic) int index;
167 @property(nonatomic, retain) NSArray *items;
171 @implementation RadioButton
176 - (id) initWithIndex:(int)_index items:_items
178 self = [super initWithFrame:CGRectZero];
180 items = [_items retain];
182 [self setText: [[items objectAtIndex:index] objectAtIndex:0]];
183 [self setBackgroundColor:[UIColor clearColor]];
192 # endif // !USE_PICKER_VIEW
195 # pragma mark Implementing labels with clickable links
197 #if defined(USE_IPHONE) && defined(USE_HTML_LABELS)
199 @interface HTMLLabel : UIView <UIWebViewDelegate>
206 @property(nonatomic, retain) NSString *html;
207 @property(nonatomic, retain) UIWebView *webView;
209 - (id) initWithHTML:(NSString *)h font:(UIFont *)f;
210 - (id) initWithText:(NSString *)t font:(UIFont *)f;
211 - (void) setHTML:(NSString *)h;
212 - (void) setText:(NSString *)t;
217 @implementation HTMLLabel
222 - (id) initWithHTML:(NSString *)h font:(UIFont *)f
225 if (! self) return 0;
227 webView = [[UIWebView alloc] init];
228 webView.delegate = self;
229 webView.dataDetectorTypes = UIDataDetectorTypeNone;
230 self. autoresizingMask = (UIViewAutoresizingFlexibleWidth |
231 UIViewAutoresizingFlexibleHeight);
232 webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth |
233 UIViewAutoresizingFlexibleHeight);
234 [self addSubview: webView];
239 - (id) initWithText:(NSString *)t font:(UIFont *)f
241 self = [self initWithHTML:@"" font:f];
242 if (! self) return 0;
248 - (void) setHTML: (NSString *)h
252 if (html) [html release];
255 [NSString stringWithFormat:
256 @"<!DOCTYPE HTML PUBLIC "
257 "\"-//W3C//DTD HTML 4.01 Transitional//EN\""
258 " \"http://www.w3.org/TR/html4/loose.dtd\">"
261 // "<META NAME=\"viewport\" CONTENT=\""
262 // "width=device-width"
263 // "initial-scale=1.0;"
264 // "maximum-scale=1.0;\">"
268 " margin: 0; padding: 0; border: 0;"
269 " font-family: \"%@\";"
270 " font-size: %.4fpx;" // Must be "px", not "pt"!
271 " line-height: %.4fpx;" // And no spaces before it.
284 [webView loadHTMLString:h2 baseURL:[NSURL URLWithString:@""]];
288 static char *anchorize (const char *url);
290 - (void) setText: (NSString *)t
292 t = [t stringByReplacingOccurrencesOfString:@"&" withString:@"&"];
293 t = [t stringByReplacingOccurrencesOfString:@"<" withString:@"<"];
294 t = [t stringByReplacingOccurrencesOfString:@">" withString:@">"];
295 t = [t stringByReplacingOccurrencesOfString:@"\n\n" withString:@" <P> "];
296 t = [t stringByReplacingOccurrencesOfString:@"<P> "
297 withString:@"<P> "];
298 t = [t stringByReplacingOccurrencesOfString:@"\n "
299 withString:@"<BR> "];
303 [t componentsSeparatedByCharactersInSet:
304 [NSCharacterSet whitespaceAndNewlineCharacterSet]]) {
305 if ([s hasPrefix:@"http://"] ||
306 [s hasPrefix:@"https://"]) {
307 char *anchor = anchorize ([s cStringUsingEncoding:NSUTF8StringEncoding]);
308 NSString *a2 = [NSString stringWithCString: anchor
309 encoding: NSUTF8StringEncoding];
310 s = [NSString stringWithFormat: @"<A HREF=\"%@\">%@</A><BR>", s, a2];
313 h = [NSString stringWithFormat: @"%@ %@", h, s];
319 -(BOOL) webView:(UIWebView *)wv
320 shouldStartLoadWithRequest:(NSURLRequest *)req
321 navigationType:(UIWebViewNavigationType)type
323 // Force clicked links to open in Safari, not in this window.
324 if (type == UIWebViewNavigationTypeLinkClicked) {
325 [[UIApplication sharedApplication] openURL:[req URL]];
332 - (void) setFrame: (CGRect)r
337 [webView setFrame: r];
338 [self setHTML: html];
343 - (NSString *) stripTags:(NSString *)str
345 NSString *result = @"";
347 str = [str stringByReplacingOccurrencesOfString:@"<P>"
348 withString:@"<BR><BR>"
349 options:NSCaseInsensitiveSearch
350 range:NSMakeRange(0, [str length])];
351 str = [str stringByReplacingOccurrencesOfString:@"<BR>"
353 options:NSCaseInsensitiveSearch
354 range:NSMakeRange(0, [str length])];
356 for (NSString *s in [str componentsSeparatedByString: @"<"]) {
357 NSRange r = [s rangeOfString:@">"];
359 s = [s substringFromIndex: r.location + r.length];
360 result = [result stringByAppendingString: s];
368 CGRect r = [self frame];
370 /* It would be sensible to just ask the UIWebView how tall the page is,
371 instead of hoping that NSString and UIWebView measure fonts and do
372 wrapping in exactly the same way, but I can't make that work.
373 Maybe because it loads async?
376 r.size.height = [[webView
377 stringByEvaluatingJavaScriptFromString:
378 @"document.body.offsetHeight"]
381 NSString *text = [self stripTags: html];
384 s = [text sizeWithFont: font
386 lineBreakMode:NSLineBreakByWordWrapping];
388 // GAAAH. Add one more line, or the UIWebView is still scrollable!
389 // The text is sized right, but it lets you scroll it up anyway.
390 s.height += [font pointSize];
392 r.size.height = s.height;
409 #endif // USE_IPHONE && USE_HTML_LABELS
412 @interface XScreenSaverConfigSheet (Private)
414 - (void)traverseChildren:(NSXMLNode *)node on:(NSView *)parent;
417 - (void) placeChild: (NSView *)c on:(NSView *)p right:(BOOL)r;
418 - (void) placeChild: (NSView *)c on:(NSView *)p;
419 static NSView *last_child (NSView *parent);
420 static void layout_group (NSView *group, BOOL horiz_p);
422 - (void) placeChild: (NSObject *)c on:(NSView *)p right:(BOOL)r;
423 - (void) placeChild: (NSObject *)c on:(NSView *)p;
424 - (void) placeSeparator;
425 - (void) bindResource:(NSObject *)ctl key:(NSString *)k reload:(BOOL)r;
426 - (void) refreshTableView;
427 # endif // USE_IPHONE
432 @implementation XScreenSaverConfigSheet
434 # define LEFT_MARGIN 20 // left edge of window
435 # define COLUMN_SPACING 10 // gap between e.g. labels and text fields
436 # define LEFT_LABEL_WIDTH 70 // width of all left labels
437 # define LINE_SPACING 10 // leading between each line
439 # define FONT_SIZE 17 // Magic hardcoded UITableView font size.
441 #pragma mark Talking to the resource database
444 /* Normally we read resources by looking up "KEY" in the database
445 "org.jwz.xscreensaver.SAVERNAME". But in the all-in-one iPhone
446 app, everything is stored in the database "org.jwz.xscreensaver"
447 instead, so transform keys to "SAVERNAME.KEY".
449 NOTE: This is duplicated in PrefsReader.m, cause I suck.
451 - (NSString *) makeKey:(NSString *)key
454 NSString *prefix = [saver_name stringByAppendingString:@"."];
455 if (! [key hasPrefix:prefix]) // Don't double up!
456 key = [prefix stringByAppendingString:key];
462 - (NSString *) makeCKey:(const char *)key
464 return [self makeKey:[NSString stringWithCString:key
465 encoding:NSUTF8StringEncoding]];
469 /* Given a command-line option, returns the corresponding resource name.
470 Any arguments in the switch string are ignored (e.g., "-foo x").
472 - (NSString *) switchToResource:(NSString *)cmdline_switch
473 opts:(const XrmOptionDescRec *)opts_array
474 valRet:(NSString **)val_ret
478 NSAssert(cmdline_switch, @"cmdline switch is null");
479 if (! [cmdline_switch getCString:buf maxLength:sizeof(buf)
480 encoding:NSUTF8StringEncoding]) {
481 NSAssert1(0, @"unable to convert %@", cmdline_switch);
484 char *s = strpbrk(buf, " \t\r\n");
488 while (*tail && (*tail == ' ' || *tail == '\t'))
492 while (opts_array[0].option) {
493 if (!strcmp (opts_array[0].option, buf)) {
496 if (opts_array[0].argKind == XrmoptionNoArg) {
498 NSAssert1 (0, @"expected no args to switch: \"%@\"",
500 ret = opts_array[0].value;
503 NSAssert1 (0, @"expected args to switch: \"%@\"",
510 ? [NSString stringWithCString:ret
511 encoding:NSUTF8StringEncoding]
514 const char *res = opts_array[0].specifier;
515 while (*res && (*res == '.' || *res == '*'))
517 return [self makeCKey:res];
522 NSAssert1 (0, @"\"%@\" not present in options", cmdline_switch);
529 // Called when a slider is bonked.
531 - (void)sliderAction:(UISlider*)sender
533 if ([active_text_field canResignFirstResponder])
534 [active_text_field resignFirstResponder];
535 NSString *pref_key = [pref_keys objectAtIndex: [sender tag]];
537 // Hacky API. See comment in InvertedSlider.m.
538 double v = ([sender isKindOfClass: [InvertedSlider class]]
539 ? [(InvertedSlider *) sender transformedValue]
543 [userDefaultsController setInteger:v forKey:pref_key];
545 [userDefaultsController setDouble:v forKey:pref_key];
548 // Called when a checkbox/switch is bonked.
550 - (void)switchAction:(UISwitch*)sender
552 if ([active_text_field canResignFirstResponder])
553 [active_text_field resignFirstResponder];
554 NSString *pref_key = [pref_keys objectAtIndex: [sender tag]];
555 NSString *v = ([sender isOn] ? @"true" : @"false");
556 [userDefaultsController setObject:v forKey:pref_key];
559 # ifdef USE_PICKER_VIEW
560 // Called when a picker is bonked.
562 - (void)pickerView:(UIPickerView *)pv
563 didSelectRow:(NSInteger)row
564 inComponent:(NSInteger)column
566 if ([active_text_field canResignFirstResponder])
567 [active_text_field resignFirstResponder];
569 NSAssert (column == 0, @"internal error");
570 NSArray *a = [picker_values objectAtIndex: [pv tag]];
571 if (! a) return; // Too early?
572 a = [a objectAtIndex:row];
573 NSAssert (a, @"missing row");
575 //NSString *label = [a objectAtIndex:0];
576 NSString *pref_key = [a objectAtIndex:1];
577 NSObject *pref_val = [a objectAtIndex:2];
578 [userDefaultsController setObject:pref_val forKey:pref_key];
580 # else // !USE_PICKER_VIEW
582 // Called when a RadioButton is bonked.
584 - (void)radioAction:(RadioButton*)sender
586 if ([active_text_field canResignFirstResponder])
587 [active_text_field resignFirstResponder];
589 NSArray *item = [[sender items] objectAtIndex: [sender index]];
590 NSString *pref_key = [item objectAtIndex:1];
591 NSObject *pref_val = [item objectAtIndex:2];
592 [userDefaultsController setObject:pref_val forKey:pref_key];
595 - (BOOL)textFieldShouldBeginEditing:(UITextField *)tf
597 active_text_field = tf;
601 - (void)textFieldDidEndEditing:(UITextField *)tf
603 NSString *pref_key = [pref_keys objectAtIndex: [tf tag]];
604 NSString *txt = [tf text];
605 [userDefaultsController setObject:txt forKey:pref_key];
608 - (BOOL)textFieldShouldReturn:(UITextField *)tf
610 active_text_field = nil;
611 [tf resignFirstResponder];
615 # endif // !USE_PICKER_VIEW
622 - (void) okAction:(NSObject *)arg
624 [userDefaultsController commitEditing];
625 [userDefaultsController save:self];
626 [NSApp endSheet:self returnCode:NSOKButton];
630 - (void) cancelAction:(NSObject *)arg
632 [userDefaultsController revert:self];
633 [NSApp endSheet:self returnCode:NSCancelButton];
636 # endif // !USE_IPHONE
639 - (void) resetAction:(NSObject *)arg
642 [userDefaultsController revertToInitialValues:self];
645 for (NSString *key in defaultOptions) {
646 NSObject *val = [defaultOptions objectForKey:key];
647 [userDefaultsController setObject:val forKey:key];
650 for (UIControl *ctl in pref_ctls) {
651 NSString *pref_key = [pref_keys objectAtIndex: ctl.tag];
652 [self bindResource:ctl key:pref_key reload:YES];
655 [self refreshTableView];
656 # endif // USE_IPHONE
660 /* Connects a control (checkbox, etc) to the corresponding preferences key.
662 - (void) bindResource:(NSObject *)control key:(NSString *)pref_key
663 reload:(BOOL)reload_p
666 NSString *bindto = ([control isKindOfClass:[NSPopUpButton class]]
668 : ([control isKindOfClass:[NSMatrix class]]
672 toObject:userDefaultsController
673 withKeyPath:[@"values." stringByAppendingString: pref_key]
677 NSObject *val = [userDefaultsController objectForKey:pref_key];
681 if ([val isKindOfClass:[NSString class]]) {
682 sval = (NSString *) val;
683 if (NSOrderedSame == [sval caseInsensitiveCompare:@"true"] ||
684 NSOrderedSame == [sval caseInsensitiveCompare:@"yes"] ||
685 NSOrderedSame == [sval caseInsensitiveCompare:@"1"])
688 dval = [sval doubleValue];
689 } else if ([val isKindOfClass:[NSNumber class]]) {
690 // NSBoolean (__NSCFBoolean) is really NSNumber.
691 dval = [(NSNumber *) val doubleValue];
692 sval = [(NSNumber *) val stringValue];
695 if ([control isKindOfClass:[UISlider class]]) {
696 sel = @selector(sliderAction:);
697 // Hacky API. See comment in InvertedSlider.m.
698 if ([control isKindOfClass:[InvertedSlider class]])
699 [(InvertedSlider *) control setTransformedValue: dval];
701 [(UISlider *) control setValue: dval];
702 } else if ([control isKindOfClass:[UISwitch class]]) {
703 sel = @selector(switchAction:);
704 [(UISwitch *) control setOn: ((int) dval != 0)];
705 # ifdef USE_PICKER_VIEW
706 } else if ([control isKindOfClass:[UIPickerView class]]) {
708 [(UIPickerView *) control selectRow:((int)dval) inComponent:0
710 # else // !USE_PICKER_VIEW
711 } else if ([control isKindOfClass:[RadioButton class]]) {
712 sel = 0; // radioAction: sent from didSelectRowAtIndexPath.
713 } else if ([control isKindOfClass:[UITextField class]]) {
715 [(UITextField *) control setText: sval];
716 # endif // !USE_PICKER_VIEW
718 NSAssert (0, @"unknown class");
721 // NSLog(@"\"%@\" = \"%@\" [%@, %.1f]", pref_key, val, [val class], dval);
725 pref_keys = [[NSMutableArray arrayWithCapacity:10] retain];
726 pref_ctls = [[NSMutableArray arrayWithCapacity:10] retain];
729 [pref_keys addObject: [self makeKey:pref_key]];
730 [pref_ctls addObject: control];
731 ((UIControl *) control).tag = [pref_keys count] - 1;
734 [(UIControl *) control addTarget:self action:sel
735 forControlEvents:UIControlEventValueChanged];
739 # endif // USE_IPHONE
742 NSObject *def = [[userDefaultsController defaults] objectForKey:pref_key];
743 NSString *s = [NSString stringWithFormat:@"bind: \"%@\"", pref_key];
744 s = [s stringByPaddingToLength:18 withString:@" " startingAtIndex:0];
745 s = [NSString stringWithFormat:@"%@ = \"%@\"", s, def];
746 s = [s stringByPaddingToLength:28 withString:@" " startingAtIndex:0];
747 NSLog (@"%@ %@/%@", s, [def class], [control class]);
752 - (void) bindResource:(NSObject *)control key:(NSString *)pref_key
754 [self bindResource:(NSObject *)control key:(NSString *)pref_key reload:NO];
759 - (void) bindSwitch:(NSObject *)control
760 cmdline:(NSString *)cmd
762 [self bindResource:control
763 key:[self switchToResource:cmd opts:opts valRet:0]];
767 #pragma mark Text-manipulating utilities
771 unwrap (NSString *text)
773 // Unwrap lines: delete \n but do not delete \n\n.
775 NSArray *lines = [text componentsSeparatedByString:@"\n"];
776 int nlines = [lines count];
780 text = @"\n"; // start with one blank line
782 // skip trailing blank lines in file
783 for (i = nlines-1; i > 0; i--) {
784 NSString *s = (NSString *) [lines objectAtIndex:i];
790 // skip leading blank lines in file
791 for (i = 0; i < nlines; i++) {
792 NSString *s = (NSString *) [lines objectAtIndex:i];
799 for (; i < nlines; i++) {
800 NSString *s = (NSString *) [lines objectAtIndex:i];
801 if ([s length] == 0) {
802 text = [text stringByAppendingString:@"\n\n"];
804 } else if ([s characterAtIndex:0] == ' ' ||
805 [s hasPrefix:@"Copyright "] ||
806 [s hasPrefix:@"http://"]) {
807 // don't unwrap if the following line begins with whitespace,
808 // or with the word "Copyright", or if it begins with a URL.
810 text = [text stringByAppendingString:@"\n"];
811 text = [text stringByAppendingString:s];
816 text = [text stringByAppendingString:@" "];
817 text = [text stringByAppendingString:s];
828 /* Makes the text up to the first comma be bold.
831 boldify (NSText *nstext)
833 NSString *text = [nstext string];
834 NSRange r = [text rangeOfString:@"," options:0];
835 r.length = r.location+1;
839 NSFont *font = [nstext font];
840 font = [NSFont boldSystemFontOfSize:[font pointSize]];
841 [nstext setFont:font range:r];
843 # endif // !USE_IPHONE
846 /* Creates a human-readable anchor to put on a URL.
849 anchorize (const char *url)
851 const char *wiki = "http://en.wikipedia.org/wiki/";
852 const char *math = "http://mathworld.wolfram.com/";
853 if (!strncmp (wiki, url, strlen(wiki))) {
854 char *anchor = (char *) malloc (strlen(url) * 3 + 10);
855 strcpy (anchor, "Wikipedia: \"");
856 const char *in = url + strlen(wiki);
857 char *out = anchor + strlen(anchor);
861 } else if (*in == '#') {
864 } else if (*in == '%') {
870 sscanf (hex, "%x", &n);
882 } else if (!strncmp (math, url, strlen(math))) {
883 char *anchor = (char *) malloc (strlen(url) * 3 + 10);
884 strcpy (anchor, "MathWorld: \"");
885 const char *start = url + strlen(wiki);
886 const char *in = start;
887 char *out = anchor + strlen(anchor);
891 } else if (in != start && *in >= 'A' && *in <= 'Z') {
894 } else if (!strncmp (in, ".htm", 4)) {
911 #if !defined(USE_IPHONE) || !defined(USE_HTML_LABELS)
913 /* Converts any http: URLs in the given text field to clickable links.
916 hreffify (NSText *nstext)
919 NSString *text = [nstext string];
920 [nstext setRichText:YES];
922 NSString *text = [nstext text];
925 int L = [text length];
926 NSRange start; // range is start-of-search to end-of-string
929 while (start.location < L) {
931 // Find the beginning of a URL...
933 NSRange r2 = [text rangeOfString:@"http://" options:0 range:start];
934 if (r2.location == NSNotFound)
937 // Next time around, start searching after this.
938 start.location = r2.location + r2.length;
939 start.length = L - start.location;
941 // Find the end of a URL (whitespace or EOF)...
943 NSRange r3 = [text rangeOfCharacterFromSet:
944 [NSCharacterSet whitespaceAndNewlineCharacterSet]
945 options:0 range:start];
946 if (r3.location == NSNotFound) // EOF
947 r3.location = L, r3.length = 0;
949 // Next time around, start searching after this.
950 start.location = r3.location;
951 start.length = L - start.location;
953 // Set r2 to the start/length of this URL.
954 r2.length = start.location - r2.location;
957 NSString *nsurl = [text substringWithRange:r2];
958 const char *url = [nsurl UTF8String];
960 // If this is a Wikipedia URL, make the linked text be prettier.
962 char *anchor = anchorize(url);
966 // Construct the RTF corresponding to <A HREF="url">anchor</A>
968 const char *fmt = "{\\field{\\*\\fldinst{HYPERLINK \"%s\"}}%s}";
969 char *rtf = malloc (strlen (fmt) + strlen(url) + strlen(anchor) + 10);
970 sprintf (rtf, fmt, url, anchor);
972 NSData *rtfdata = [NSData dataWithBytesNoCopy:rtf length:strlen(rtf)];
973 [nstext replaceCharactersInRange:r2 withRTF:rtfdata];
975 # else // !USE_IPHONE
976 // *anchor = 0; // Omit Wikipedia anchor
977 text = [text stringByReplacingCharactersInRange:r2
978 withString:[NSString stringWithCString:anchor
979 encoding:NSUTF8StringEncoding]];
980 // text = [text stringByReplacingOccurrencesOfString:@"\n\n\n"
981 // withString:@"\n\n"];
982 # endif // !USE_IPHONE
986 int L2 = [text length]; // might have changed
987 start.location -= (L - L2);
992 [nstext setText:text];
997 #endif /* !USE_IPHONE || !USE_HTML_LABELS */
1001 #pragma mark Creating controls from XML
1004 /* Parse the attributes of an XML tag into a dictionary.
1005 For input, the dictionary should have as attributes the keys, each
1006 with @"" as their value.
1007 On output, the dictionary will set the keys to the values specified,
1008 and keys that were not specified will not be present in the dictionary.
1009 Warnings are printed if there are duplicate or unknown attributes.
1011 - (void) parseAttrs:(NSMutableDictionary *)dict node:(NSXMLNode *)node
1013 NSArray *attrs = [(NSXMLElement *) node attributes];
1014 int n = [attrs count];
1017 // For each key in the dictionary, fill in the dict with the corresponding
1018 // value. The value @"" is assumed to mean "un-set". Issue a warning if
1019 // an attribute is specified twice.
1021 for (i = 0; i < n; i++) {
1022 NSXMLNode *attr = [attrs objectAtIndex:i];
1023 NSString *key = [attr name];
1024 NSString *val = [attr objectValue];
1025 NSString *old = [dict objectForKey:key];
1028 NSAssert2 (0, @"unknown attribute \"%@\" in \"%@\"", key, [node name]);
1029 } else if ([old length] != 0) {
1030 NSAssert3 (0, @"duplicate %@: \"%@\", \"%@\"", key, old, val);
1032 [dict setValue:val forKey:key];
1036 // Remove from the dictionary any keys whose value is still @"",
1037 // meaning there was no such attribute specified.
1039 NSArray *keys = [dict allKeys];
1041 for (i = 0; i < n; i++) {
1042 NSString *key = [keys objectAtIndex:i];
1043 NSString *val = [dict objectForKey:key];
1044 if ([val length] == 0)
1045 [dict removeObjectForKey:key];
1049 // Kludge for starwars.xml:
1050 // If there is a "_low-label" and no "_label", but "_low-label" contains
1051 // spaces, divide them.
1052 NSString *lab = [dict objectForKey:@"_label"];
1053 NSString *low = [dict objectForKey:@"_low-label"];
1056 [[[low stringByTrimmingCharactersInSet:
1057 [NSCharacterSet whitespaceAndNewlineCharacterSet]]
1058 componentsSeparatedByString: @" "]
1059 filteredArrayUsingPredicate:
1060 [NSPredicate predicateWithFormat:@"length > 0"]];
1061 if (split && [split count] == 2) {
1062 [dict setValue:[split objectAtIndex:0] forKey:@"_label"];
1063 [dict setValue:[split objectAtIndex:1] forKey:@"_low-label"];
1066 # endif // USE_IPHONE
1070 /* Handle the options on the top level <xscreensaver> tag.
1072 - (NSString *) parseXScreenSaverTag:(NSXMLNode *)node
1074 NSMutableDictionary *dict =
1075 [NSMutableDictionary dictionaryWithObjectsAndKeys:
1080 [self parseAttrs:dict node:node];
1081 NSString *name = [dict objectForKey:@"name"];
1082 NSString *label = [dict objectForKey:@"_label"];
1084 NSAssert1 (label, @"no _label in %@", [node name]);
1085 NSAssert1 (name, @"no name in \"%@\"", label);
1090 /* Creates a label: an un-editable NSTextField displaying the given text.
1092 - (LABEL *) makeLabel:(NSString *)text
1095 rect.origin.x = rect.origin.y = 0;
1096 rect.size.width = rect.size.height = 10;
1098 NSTextField *lab = [[NSTextField alloc] initWithFrame:rect];
1099 [lab setSelectable:NO];
1100 [lab setEditable:NO];
1101 [lab setBezeled:NO];
1102 [lab setDrawsBackground:NO];
1103 [lab setStringValue:text];
1105 # else // USE_IPHONE
1106 UILabel *lab = [[UILabel alloc] initWithFrame:rect];
1107 [lab setText: [text stringByTrimmingCharactersInSet:
1108 [NSCharacterSet whitespaceAndNewlineCharacterSet]]];
1109 [lab setBackgroundColor:[UIColor clearColor]];
1110 [lab setNumberOfLines:0]; // unlimited
1111 // [lab setLineBreakMode:UILineBreakModeWordWrap];
1112 [lab setLineBreakMode:NSLineBreakByTruncatingHead];
1113 [lab setAutoresizingMask: (UIViewAutoresizingFlexibleWidth |
1114 UIViewAutoresizingFlexibleHeight)];
1115 # endif // USE_IPHONE
1120 /* Creates the checkbox (NSButton) described by the given XML node.
1122 - (void) makeCheckbox:(NSXMLNode *)node on:(NSView *)parent
1124 NSMutableDictionary *dict =
1125 [NSMutableDictionary dictionaryWithObjectsAndKeys:
1131 [self parseAttrs:dict node:node];
1132 NSString *label = [dict objectForKey:@"_label"];
1133 NSString *arg_set = [dict objectForKey:@"arg-set"];
1134 NSString *arg_unset = [dict objectForKey:@"arg-unset"];
1137 NSAssert1 (0, @"no _label in %@", [node name]);
1140 if (!arg_set && !arg_unset) {
1141 NSAssert1 (0, @"neither arg-set nor arg-unset provided in \"%@\"",
1144 if (arg_set && arg_unset) {
1145 NSAssert1 (0, @"only one of arg-set and arg-unset may be used in \"%@\"",
1149 // sanity-check the choice of argument names.
1151 if (arg_set && ([arg_set hasPrefix:@"-no-"] ||
1152 [arg_set hasPrefix:@"--no-"]))
1153 NSLog (@"arg-set should not be a \"no\" option in \"%@\": %@",
1155 if (arg_unset && (![arg_unset hasPrefix:@"-no-"] &&
1156 ![arg_unset hasPrefix:@"--no-"]))
1157 NSLog(@"arg-unset should be a \"no\" option in \"%@\": %@",
1161 rect.origin.x = rect.origin.y = 0;
1162 rect.size.width = rect.size.height = 10;
1166 NSButton *button = [[NSButton alloc] initWithFrame:rect];
1167 [button setButtonType:NSSwitchButton];
1168 [button setTitle:label];
1170 [self placeChild:button on:parent];
1172 # else // USE_IPHONE
1174 LABEL *lab = [self makeLabel:label];
1175 [self placeChild:lab on:parent];
1176 UISwitch *button = [[UISwitch alloc] initWithFrame:rect];
1177 [self placeChild:button on:parent right:YES];
1180 # endif // USE_IPHONE
1182 [self bindSwitch:button cmdline:(arg_set ? arg_set : arg_unset)];
1187 /* Creates the number selection control described by the given XML node.
1188 If "type=slider", it's an NSSlider.
1189 If "type=spinbutton", it's a text field with up/down arrows next to it.
1191 - (void) makeNumberSelector:(NSXMLNode *)node on:(NSView *)parent
1193 NSMutableDictionary *dict =
1194 [NSMutableDictionary dictionaryWithObjectsAndKeys:
1198 @"", @"_high-label",
1206 [self parseAttrs:dict node:node];
1207 NSString *label = [dict objectForKey:@"_label"];
1208 NSString *low_label = [dict objectForKey:@"_low-label"];
1209 NSString *high_label = [dict objectForKey:@"_high-label"];
1210 NSString *type = [dict objectForKey:@"type"];
1211 NSString *arg = [dict objectForKey:@"arg"];
1212 NSString *low = [dict objectForKey:@"low"];
1213 NSString *high = [dict objectForKey:@"high"];
1214 NSString *def = [dict objectForKey:@"default"];
1215 NSString *cvt = [dict objectForKey:@"convert"];
1217 NSAssert1 (arg, @"no arg in %@", label);
1218 NSAssert1 (type, @"no type in %@", label);
1221 NSAssert1 (0, @"no low in %@", [node name]);
1225 NSAssert1 (0, @"no high in %@", [node name]);
1229 NSAssert1 (0, @"no default in %@", [node name]);
1232 if (cvt && ![cvt isEqualToString:@"invert"]) {
1233 NSAssert1 (0, @"if provided, \"convert\" must be \"invert\" in %@",
1237 // If either the min or max field contains a decimal point, then this
1238 // option may have a floating point value; otherwise, it is constrained
1239 // to be an integer.
1241 NSCharacterSet *dot =
1242 [NSCharacterSet characterSetWithCharactersInString:@"."];
1243 BOOL float_p = ([low rangeOfCharacterFromSet:dot].location != NSNotFound ||
1244 [high rangeOfCharacterFromSet:dot].location != NSNotFound);
1246 if ([type isEqualToString:@"slider"]
1247 # ifdef USE_IPHONE // On iPhone, we use sliders for all numeric values.
1248 || [type isEqualToString:@"spinbutton"]
1253 rect.origin.x = rect.origin.y = 0;
1254 rect.size.width = 150;
1255 rect.size.height = 23; // apparent min height for slider with ticks...
1257 slider = [[InvertedSlider alloc] initWithFrame:rect
1259 integers: !float_p];
1260 [slider setMaxValue:[high doubleValue]];
1261 [slider setMinValue:[low doubleValue]];
1263 int range = [slider maxValue] - [slider minValue] + 1;
1266 while (range2 > max_ticks)
1269 // If we have elided ticks, leave it at the max number of ticks.
1270 if (range != range2 && range2 < max_ticks)
1273 // If it's a float, always display the max number of ticks.
1274 if (float_p && range2 < max_ticks)
1278 [slider setNumberOfTickMarks:range2];
1280 [slider setAllowsTickMarkValuesOnly:
1281 (range == range2 && // we are showing the actual number of ticks
1282 !float_p)]; // and we want integer results
1283 # endif // !USE_IPHONE
1285 // #### Note: when the slider's range is large enough that we aren't
1286 // showing all possible ticks, the slider's value is not constrained
1287 // to be an integer, even though it should be...
1288 // Maybe we need to use a value converter or something?
1292 lab = [self makeLabel:label];
1293 [self placeChild:lab on:parent];
1296 CGFloat s = [NSFont systemFontSize] + 4;
1297 [lab setFont:[NSFont boldSystemFontOfSize:s]];
1304 lab = [self makeLabel:low_label];
1305 [lab setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
1307 [lab setAlignment:1]; // right aligned
1309 if (rect.size.width < LEFT_LABEL_WIDTH)
1310 rect.size.width = LEFT_LABEL_WIDTH; // make all left labels same size
1311 rect.size.height = [slider frame].size.height;
1312 [lab setFrame:rect];
1313 [self placeChild:lab on:parent];
1314 # else // USE_IPHONE
1315 [lab setTextAlignment: NSTextAlignmentRight];
1316 [self placeChild:lab on:parent right:(label ? YES : NO)];
1317 # endif // USE_IPHONE
1323 [self placeChild:slider on:parent right:(low_label ? YES : NO)];
1324 # else // USE_IPHONE
1325 [self placeChild:slider on:parent right:(label || low_label ? YES : NO)];
1326 # endif // USE_IPHONE
1329 // Make left label be same height as slider.
1331 rect.size.height = [slider frame].size.height;
1332 [lab setFrame:rect];
1336 rect = [slider frame];
1337 if (rect.origin.x < LEFT_LABEL_WIDTH)
1338 rect.origin.x = LEFT_LABEL_WIDTH; // make unlabelled sliders line up too
1339 [slider setFrame:rect];
1343 lab = [self makeLabel:high_label];
1344 [lab setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
1347 // Make right label be same height as slider.
1348 rect.size.height = [slider frame].size.height;
1349 [lab setFrame:rect];
1350 [self placeChild:lab on:parent right:YES];
1354 [self bindSwitch:slider cmdline:arg];
1357 #ifndef USE_IPHONE // On iPhone, we use sliders for all numeric values.
1359 } else if ([type isEqualToString:@"spinbutton"]) {
1362 NSAssert1 (0, @"no _label in spinbutton %@", [node name]);
1365 NSAssert1 (!low_label,
1366 @"low-label not allowed in spinbutton \"%@\"", [node name]);
1367 NSAssert1 (!high_label,
1368 @"high-label not allowed in spinbutton \"%@\"", [node name]);
1369 NSAssert1 (!cvt, @"convert not allowed in spinbutton \"%@\"",
1373 rect.origin.x = rect.origin.y = 0;
1374 rect.size.width = rect.size.height = 10;
1376 NSTextField *txt = [[NSTextField alloc] initWithFrame:rect];
1377 [txt setStringValue:@"0000.0"];
1379 [txt setStringValue:@""];
1382 LABEL *lab = [self makeLabel:label];
1383 //[lab setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
1384 [lab setAlignment:1]; // right aligned
1386 if (rect.size.width < LEFT_LABEL_WIDTH)
1387 rect.size.width = LEFT_LABEL_WIDTH; // make all left labels same size
1388 rect.size.height = [txt frame].size.height;
1389 [lab setFrame:rect];
1390 [self placeChild:lab on:parent];
1394 [self placeChild:txt on:parent right:(label ? YES : NO)];
1398 if (rect.origin.x < LEFT_LABEL_WIDTH)
1399 rect.origin.x = LEFT_LABEL_WIDTH; // make unlabelled spinbtns line up
1400 [txt setFrame:rect];
1403 rect.size.width = rect.size.height = 10;
1404 NSStepper *step = [[NSStepper alloc] initWithFrame:rect];
1406 [self placeChild:step on:parent right:YES];
1407 rect = [step frame];
1408 rect.origin.x -= COLUMN_SPACING; // this one goes close
1409 rect.origin.y += ([txt frame].size.height - rect.size.height) / 2;
1410 [step setFrame:rect];
1412 [step setMinValue:[low doubleValue]];
1413 [step setMaxValue:[high doubleValue]];
1414 [step setAutorepeat:YES];
1415 [step setValueWraps:NO];
1417 double range = [high doubleValue] - [low doubleValue];
1419 [step setIncrement:range / 10.0];
1420 else if (range >= 500)
1421 [step setIncrement:range / 100.0];
1423 [step setIncrement:1.0];
1425 NSNumberFormatter *fmt = [[[NSNumberFormatter alloc] init] autorelease];
1426 [fmt setFormatterBehavior:NSNumberFormatterBehavior10_4];
1427 [fmt setNumberStyle:NSNumberFormatterDecimalStyle];
1428 [fmt setMinimum:[NSNumber numberWithDouble:[low doubleValue]]];
1429 [fmt setMaximum:[NSNumber numberWithDouble:[high doubleValue]]];
1430 [fmt setMinimumFractionDigits: (float_p ? 1 : 0)];
1431 [fmt setMaximumFractionDigits: (float_p ? 2 : 0)];
1433 [fmt setGeneratesDecimalNumbers:float_p];
1434 [[txt cell] setFormatter:fmt];
1436 [self bindSwitch:step cmdline:arg];
1437 [self bindSwitch:txt cmdline:arg];
1442 # endif // USE_IPHONE
1445 NSAssert2 (0, @"unknown type \"%@\" in \"%@\"", type, label);
1452 set_menu_item_object (NSMenuItem *item, NSObject *obj)
1454 /* If the object associated with this menu item looks like a boolean,
1455 store an NSNumber instead of an NSString, since that's what
1456 will be in the preferences (due to similar logic in PrefsReader).
1458 if ([obj isKindOfClass:[NSString class]]) {
1459 NSString *string = (NSString *) obj;
1460 if (NSOrderedSame == [string caseInsensitiveCompare:@"true"] ||
1461 NSOrderedSame == [string caseInsensitiveCompare:@"yes"])
1462 obj = [NSNumber numberWithBool:YES];
1463 else if (NSOrderedSame == [string caseInsensitiveCompare:@"false"] ||
1464 NSOrderedSame == [string caseInsensitiveCompare:@"no"])
1465 obj = [NSNumber numberWithBool:NO];
1470 [item setRepresentedObject:obj];
1471 //NSLog (@"menu item \"%@\" = \"%@\" %@", [item title], obj, [obj class]);
1473 # endif // !USE_IPHONE
1476 /* Creates the popup menu described by the given XML node (and its children).
1478 - (void) makeOptionMenu:(NSXMLNode *)node on:(NSView *)parent
1480 NSArray *children = [node children];
1481 int i, count = [children count];
1484 NSAssert1 (0, @"no menu items in \"%@\"", [node name]);
1488 // get the "id" attribute off the <select> tag.
1490 NSMutableDictionary *dict =
1491 [NSMutableDictionary dictionaryWithObjectsAndKeys:
1494 [self parseAttrs:dict node:node];
1497 rect.origin.x = rect.origin.y = 0;
1498 rect.size.width = 10;
1499 rect.size.height = 10;
1501 NSString *menu_key = nil; // the resource key used by items in this menu
1504 // #### "Build and Analyze" says that all of our widgets leak, because it
1505 // seems to not realize that placeChild -> addSubview retains them.
1506 // Not sure what to do to make these warnings go away.
1508 NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:rect
1510 NSMenuItem *def_item = nil;
1511 float max_width = 0;
1513 # else // USE_IPHONE
1515 NSString *def_item = nil;
1517 rect.size.width = 0;
1518 rect.size.height = 0;
1519 # ifdef USE_PICKER_VIEW
1520 UIPickerView *popup = [[[UIPickerView alloc] initWithFrame:rect] retain];
1521 popup.delegate = self;
1522 popup.dataSource = self;
1523 # endif // !USE_PICKER_VIEW
1524 NSMutableArray *items = [NSMutableArray arrayWithCapacity:10];
1526 # endif // USE_IPHONE
1528 for (i = 0; i < count; i++) {
1529 NSXMLNode *child = [children objectAtIndex:i];
1531 if ([child kind] == NSXMLCommentKind)
1533 if ([child kind] != NSXMLElementKind) {
1534 // NSAssert2 (0, @"weird XML node kind: %d: %@", (int)[child kind], node);
1538 // get the "id", "_label", and "arg-set" attrs off of the <option> tags.
1540 NSMutableDictionary *dict2 =
1541 [NSMutableDictionary dictionaryWithObjectsAndKeys:
1546 [self parseAttrs:dict2 node:child];
1547 NSString *label = [dict2 objectForKey:@"_label"];
1548 NSString *arg_set = [dict2 objectForKey:@"arg-set"];
1551 NSAssert1 (0, @"no _label in %@", [child name]);
1556 // create the menu item (and then get a pointer to it)
1557 [popup addItemWithTitle:label];
1558 NSMenuItem *item = [popup itemWithTitle:label];
1559 # endif // USE_IPHONE
1562 NSString *this_val = NULL;
1563 NSString *this_key = [self switchToResource: arg_set
1566 NSAssert1 (this_val, @"this_val null for %@", arg_set);
1567 if (menu_key && ![menu_key isEqualToString:this_key])
1569 @"multiple resources in menu: \"%@\" vs \"%@\" = \"%@\"",
1570 menu_key, this_key, this_val);
1572 menu_key = this_key;
1574 /* If this menu has the cmd line "-mode foo" then set this item's
1575 value to "foo" (the menu itself will be bound to e.g. "modeString")
1578 set_menu_item_object (item, this_val);
1580 // Array holds ["Label", "resource-key", "resource-val"].
1581 [items addObject:[NSMutableArray arrayWithObjects:
1582 label, @"", this_val, nil]];
1586 // no arg-set -- only one menu item can be missing that.
1587 NSAssert1 (!def_item, @"no arg-set in \"%@\"", label);
1592 // Array holds ["Label", "resource-key", "resource-val"].
1593 [items addObject:[NSMutableArray arrayWithObjects:
1594 label, @"", @"", nil]];
1598 /* make sure the menu button has room for the text of this item,
1599 and remember the greatest width it has reached.
1602 [popup setTitle:label];
1604 NSRect r = [popup frame];
1605 if (r.size.width > max_width) max_width = r.size.width;
1606 # endif // USE_IPHONE
1610 NSAssert1 (0, @"no switches in menu \"%@\"", [dict objectForKey:@"id"]);
1614 /* We've added all of the menu items. If there was an item with no
1615 command-line switch, then it's the item that represents the default
1616 value. Now we must bind to that item as well... (We have to bind
1617 this one late, because if it was the first item, then we didn't
1618 yet know what resource was associated with this menu.)
1621 NSObject *def_obj = [defaultOptions objectForKey:menu_key];
1623 @"no default value for resource \"%@\" in menu item \"%@\"",
1633 set_menu_item_object (def_item, def_obj);
1634 # else // !USE_IPHONE
1635 for (NSMutableArray *a in items) {
1636 // Make sure each array contains the resource key.
1637 [a replaceObjectAtIndex:1 withObject:menu_key];
1638 // Make sure the default item contains the default resource value.
1639 if (def_obj && def_item &&
1640 [def_item isEqualToString:[a objectAtIndex:0]])
1641 [a replaceObjectAtIndex:2 withObject:def_obj];
1643 # endif // !USE_IPHONE
1647 # ifdef USE_PICKER_VIEW
1648 /* Finish tweaking the menu button itself.
1651 [popup setTitle:[def_item title]];
1652 NSRect r = [popup frame];
1653 r.size.width = max_width;
1655 # endif // USE_PICKER_VIEW
1658 # if !defined(USE_IPHONE) || defined(USE_PICKER_VIEW)
1659 [self placeChild:popup on:parent];
1660 [self bindResource:popup key:menu_key];
1665 # ifdef USE_PICKER_VIEW
1666 // Store the items for this picker in the picker_values array.
1667 // This is so fucking stupid.
1669 int menu_number = [pref_keys count] - 1;
1670 if (! picker_values)
1671 picker_values = [[NSMutableArray arrayWithCapacity:menu_number] retain];
1672 while ([picker_values count] <= menu_number)
1673 [picker_values addObject:[NSArray arrayWithObjects: nil]];
1674 [picker_values replaceObjectAtIndex:menu_number withObject:items];
1675 [popup reloadAllComponents];
1677 # else // !USE_PICKER_VIEW
1679 [self placeSeparator];
1682 for (NSArray *item in items) {
1683 RadioButton *b = [[RadioButton alloc] initWithIndex:i
1685 [b setLineBreakMode:NSLineBreakByTruncatingHead];
1686 [b setFont:[NSFont boldSystemFontOfSize: FONT_SIZE]];
1687 [self placeChild:b on:parent];
1691 [self placeSeparator];
1693 # endif // !USE_PICKER_VIEW
1694 # endif // !USE_IPHONE
1699 /* Creates an uneditable, wrapping NSTextField to display the given
1700 text enclosed by <description> ... </description> in the XML.
1702 - (void) makeDescLabel:(NSXMLNode *)node on:(NSView *)parent
1704 NSString *text = nil;
1705 NSArray *children = [node children];
1706 int i, count = [children count];
1708 for (i = 0; i < count; i++) {
1709 NSXMLNode *child = [children objectAtIndex:i];
1710 NSString *s = [child objectValue];
1712 text = [text stringByAppendingString:s];
1717 text = unwrap (text);
1719 NSRect rect = [parent frame];
1720 rect.origin.x = rect.origin.y = 0;
1721 rect.size.width = 200;
1722 rect.size.height = 50; // sized later
1724 NSText *lab = [[NSText alloc] initWithFrame:rect];
1725 [lab setEditable:NO];
1726 [lab setDrawsBackground:NO];
1727 [lab setHorizontallyResizable:YES];
1728 [lab setVerticallyResizable:YES];
1729 [lab setString:text];
1734 # else // USE_IPHONE
1736 # ifndef USE_HTML_LABELS
1738 UILabel *lab = [self makeLabel:text];
1739 [lab setFont:[NSFont systemFontOfSize: [NSFont systemFontSize]]];
1742 # else // USE_HTML_LABELS
1743 HTMLLabel *lab = [[HTMLLabel alloc]
1745 font:[NSFont systemFontOfSize: [NSFont systemFontSize]]];
1746 [lab setFrame:rect];
1748 # endif // USE_HTML_LABELS
1750 [self placeSeparator];
1752 # endif // USE_IPHONE
1754 [self placeChild:lab on:parent];
1759 /* Creates the NSTextField described by the given XML node.
1761 - (void) makeTextField: (NSXMLNode *)node
1762 on: (NSView *)parent
1763 withLabel: (BOOL) label_p
1764 horizontal: (BOOL) horiz_p
1766 NSMutableDictionary *dict =
1767 [NSMutableDictionary dictionaryWithObjectsAndKeys:
1772 [self parseAttrs:dict node:node];
1773 NSString *label = [dict objectForKey:@"_label"];
1774 NSString *arg = [dict objectForKey:@"arg"];
1776 if (!label && label_p) {
1777 NSAssert1 (0, @"no _label in %@", [node name]);
1781 NSAssert1 (arg, @"no arg in %@", label);
1784 rect.origin.x = rect.origin.y = 0;
1785 rect.size.width = rect.size.height = 10;
1787 NSTextField *txt = [[NSTextField alloc] initWithFrame:rect];
1791 // make the default size be around 30 columns; a typical value for
1792 // these text fields is "xscreensaver-text --cols 40".
1794 [txt setStringValue:@"123456789 123456789 123456789 "];
1796 [[txt cell] setWraps:NO];
1797 [[txt cell] setScrollable:YES];
1798 [txt setStringValue:@""];
1800 # else // USE_IPHONE
1802 txt.adjustsFontSizeToFitWidth = YES;
1803 txt.textColor = [UIColor blackColor];
1804 txt.font = [UIFont systemFontOfSize: FONT_SIZE];
1805 txt.placeholder = @"";
1806 txt.borderStyle = UITextBorderStyleRoundedRect;
1807 txt.textAlignment = NSTextAlignmentRight;
1808 txt.keyboardType = UIKeyboardTypeDefault; // Full kbd
1809 txt.autocorrectionType = UITextAutocorrectionTypeNo;
1810 txt.autocapitalizationType = UITextAutocapitalizationTypeNone;
1811 txt.clearButtonMode = UITextFieldViewModeAlways;
1812 txt.returnKeyType = UIReturnKeyDone;
1813 txt.delegate = self;
1815 [txt setEnabled: YES];
1817 rect.size.height = [txt.font lineHeight] * 1.2;
1818 [txt setFrame:rect];
1820 # endif // USE_IPHONE
1823 LABEL *lab = [self makeLabel:label];
1824 [self placeChild:lab on:parent];
1828 [self placeChild:txt on:parent right:(label ? YES : NO)];
1830 [self bindSwitch:txt cmdline:arg];
1835 /* Creates the NSTextField described by the given XML node,
1836 and hooks it up to a Choose button and a file selector widget.
1838 - (void) makeFileSelector: (NSXMLNode *)node
1839 on: (NSView *)parent
1840 dirsOnly: (BOOL) dirsOnly
1841 withLabel: (BOOL) label_p
1842 editable: (BOOL) editable_p
1844 # ifndef USE_IPHONE // No files. No selectors.
1845 NSMutableDictionary *dict =
1846 [NSMutableDictionary dictionaryWithObjectsAndKeys:
1851 [self parseAttrs:dict node:node];
1852 NSString *label = [dict objectForKey:@"_label"];
1853 NSString *arg = [dict objectForKey:@"arg"];
1855 if (!label && label_p) {
1856 NSAssert1 (0, @"no _label in %@", [node name]);
1860 NSAssert1 (arg, @"no arg in %@", label);
1863 rect.origin.x = rect.origin.y = 0;
1864 rect.size.width = rect.size.height = 10;
1866 NSTextField *txt = [[NSTextField alloc] initWithFrame:rect];
1868 // make the default size be around 20 columns.
1870 [txt setStringValue:@"123456789 123456789 "];
1872 [txt setSelectable:YES];
1873 [txt setEditable:editable_p];
1874 [txt setBezeled:editable_p];
1875 [txt setDrawsBackground:editable_p];
1876 [[txt cell] setWraps:NO];
1877 [[txt cell] setScrollable:YES];
1878 [[txt cell] setLineBreakMode:NSLineBreakByTruncatingHead];
1879 [txt setStringValue:@""];
1883 lab = [self makeLabel:label];
1884 [self placeChild:lab on:parent];
1888 [self placeChild:txt on:parent right:(label ? YES : NO)];
1890 [self bindSwitch:txt cmdline:arg];
1893 // Make the text field and label be the same height, whichever is taller.
1896 rect.size.height = ([lab frame].size.height > [txt frame].size.height
1897 ? [lab frame].size.height
1898 : [txt frame].size.height);
1899 [txt setFrame:rect];
1902 // Now put a "Choose" button next to it.
1904 rect.origin.x = rect.origin.y = 0;
1905 rect.size.width = rect.size.height = 10;
1906 NSButton *choose = [[NSButton alloc] initWithFrame:rect];
1907 [choose setTitle:@"Choose..."];
1908 [choose setBezelStyle:NSRoundedBezelStyle];
1911 [self placeChild:choose on:parent right:YES];
1913 // center the Choose button around the midpoint of the text field.
1914 rect = [choose frame];
1915 rect.origin.y = ([txt frame].origin.y +
1916 (([txt frame].size.height - rect.size.height) / 2));
1917 [choose setFrameOrigin:rect.origin];
1919 [choose setTarget:[parent window]];
1921 [choose setAction:@selector(fileSelectorChooseDirsAction:)];
1923 [choose setAction:@selector(fileSelectorChooseAction:)];
1926 # endif // !USE_IPHONE
1932 /* Runs a modal file selector and sets the text field's value to the
1933 selected file or directory.
1936 do_file_selector (NSTextField *txt, BOOL dirs_p)
1938 NSOpenPanel *panel = [NSOpenPanel openPanel];
1939 [panel setAllowsMultipleSelection:NO];
1940 [panel setCanChooseFiles:!dirs_p];
1941 [panel setCanChooseDirectories:dirs_p];
1943 NSString *file = [txt stringValue];
1944 if ([file length] <= 0) {
1945 file = NSHomeDirectory();
1947 file = [file stringByAppendingPathComponent:@"Pictures"];
1950 // NSString *dir = [file stringByDeletingLastPathComponent];
1952 int result = [panel runModalForDirectory:file //dir
1953 file:nil //[file lastPathComponent]
1955 if (result == NSOKButton) {
1956 NSArray *files = [panel filenames];
1957 file = ([files count] > 0 ? [files objectAtIndex:0] : @"");
1958 file = [file stringByAbbreviatingWithTildeInPath];
1959 [txt setStringValue:file];
1961 // Fuck me! Just setting the value of the NSTextField does not cause
1962 // that to end up in the preferences!
1964 NSDictionary *dict = [txt infoForBinding:@"value"];
1965 NSUserDefaultsController *prefs = [dict objectForKey:@"NSObservedObject"];
1966 NSString *path = [dict objectForKey:@"NSObservedKeyPath"];
1967 if ([path hasPrefix:@"values."]) // WTF.
1968 path = [path substringFromIndex:7];
1969 [[prefs values] setValue:file forKey:path];
1972 // make sure the end of the string is visible.
1973 NSText *fe = [[txt window] fieldEditor:YES forObject:txt];
1975 range.location = [file length]-3;
1977 if (! [[txt window] makeFirstResponder:[txt window]])
1978 [[txt window] endEditingFor:nil];
1979 // [[txt window] makeFirstResponder:nil];
1980 [fe setSelectedRange:range];
1981 // [tv scrollRangeToVisible:range];
1982 // [txt setNeedsDisplay:YES];
1983 // [[txt cell] setNeedsDisplay:YES];
1984 // [txt selectAll:txt];
1990 /* Returns the NSTextField that is to the left of or above the NSButton.
1992 static NSTextField *
1993 find_text_field_of_button (NSButton *button)
1995 NSView *parent = [button superview];
1996 NSArray *kids = [parent subviews];
1997 int nkids = [kids count];
2000 for (i = 0; i < nkids; i++) {
2001 NSObject *kid = [kids objectAtIndex:i];
2002 if ([kid isKindOfClass:[NSTextField class]]) {
2003 f = (NSTextField *) kid;
2004 } else if (kid == button) {
2013 - (void) fileSelectorChooseAction:(NSObject *)arg
2015 NSButton *choose = (NSButton *) arg;
2016 NSTextField *txt = find_text_field_of_button (choose);
2017 do_file_selector (txt, NO);
2020 - (void) fileSelectorChooseDirsAction:(NSObject *)arg
2022 NSButton *choose = (NSButton *) arg;
2023 NSTextField *txt = find_text_field_of_button (choose);
2024 do_file_selector (txt, YES);
2027 #endif // !USE_IPHONE
2030 - (void) makeTextLoaderControlBox:(NSXMLNode *)node on:(NSView *)parent
2035 (x) Computer name and time
2036 ( ) Text [__________________________]
2037 ( ) Text file [_________________] [Choose]
2038 ( ) URL [__________________________]
2039 ( ) Shell Cmd [__________________________]
2041 textMode -text-mode date
2042 textMode -text-mode literal textLiteral -text-literal %
2043 textMode -text-mode file textFile -text-file %
2044 textMode -text-mode url textURL -text-url %
2045 textMode -text-mode program textProgram -text-program %
2048 rect.size.width = rect.size.height = 1;
2049 rect.origin.x = rect.origin.y = 0;
2050 NSView *group = [[NSView alloc] initWithFrame:rect];
2051 NSView *rgroup = [[NSView alloc] initWithFrame:rect];
2053 Bool program_p = TRUE;
2058 // This is how you link radio buttons together.
2060 NSButtonCell *proto = [[NSButtonCell alloc] init];
2061 [proto setButtonType:NSRadioButton];
2063 rect.origin.x = rect.origin.y = 0;
2064 rect.size.width = rect.size.height = 10;
2065 NSMatrix *matrix = [[NSMatrix alloc]
2067 mode:NSRadioModeMatrix
2069 numberOfRows: 4 + (program_p ? 1 : 0)
2071 [matrix setAllowsEmptySelection:NO];
2073 NSArrayController *cnames = [[NSArrayController alloc] initWithContent:nil];
2074 [cnames addObject:@"Computer name and time"];
2075 [cnames addObject:@"Text"];
2076 [cnames addObject:@"File"];
2077 [cnames addObject:@"URL"];
2078 if (program_p) [cnames addObject:@"Shell Cmd"];
2079 [matrix bind:@"content"
2081 withKeyPath:@"arrangedObjects"
2085 [self bindSwitch:matrix cmdline:@"-text-mode %"];
2087 [self placeChild:matrix on:group];
2088 [self placeChild:rgroup on:group right:YES];
2092 # else // USE_IPHONE
2094 NSView *rgroup = parent;
2097 // <select id="textMode">
2098 // <option id="date" _label="Display date" arg-set="-text-mode date"/>
2099 // <option id="text" _label="Display text" arg-set="-text-mode literal"/>
2100 // <option id="url" _label="Display URL"/>
2103 node2 = [[NSXMLElement alloc] initWithName:@"select"];
2104 [node2 setAttributesAsDictionary:
2105 [NSDictionary dictionaryWithObjectsAndKeys:
2109 NSXMLNode *node3 = [[NSXMLElement alloc] initWithName:@"option"];
2110 [node3 setAttributesAsDictionary:
2111 [NSDictionary dictionaryWithObjectsAndKeys:
2113 @"-text-mode date", @"arg-set",
2114 @"Display the date and time", @"_label",
2116 [node3 setParent: node2];
2119 node3 = [[NSXMLElement alloc] initWithName:@"option"];
2120 [node3 setAttributesAsDictionary:
2121 [NSDictionary dictionaryWithObjectsAndKeys:
2123 @"-text-mode literal", @"arg-set",
2124 @"Display static text", @"_label",
2126 [node3 setParent: node2];
2129 node3 = [[NSXMLElement alloc] initWithName:@"option"];
2130 [node3 setAttributesAsDictionary:
2131 [NSDictionary dictionaryWithObjectsAndKeys:
2133 @"Display the contents of a URL", @"_label",
2135 [node3 setParent: node2];
2138 [self makeOptionMenu:node2 on:rgroup];
2140 # endif // USE_IPHONE
2143 // <string id="textLiteral" _label="" arg-set="-text-literal %"/>
2144 node2 = [[NSXMLElement alloc] initWithName:@"string"];
2145 [node2 setAttributesAsDictionary:
2146 [NSDictionary dictionaryWithObjectsAndKeys:
2147 @"textLiteral", @"id",
2148 @"-text-literal %", @"arg",
2150 @"Text to display", @"_label",
2153 [self makeTextField:node2 on:rgroup
2161 // rect = [last_child(rgroup) frame];
2163 /* // trying to make the text fields be enabled only when the checkbox is on..
2164 control = last_child (rgroup);
2165 [control bind:@"enabled"
2166 toObject:[matrix cellAtRow:1 column:0]
2167 withKeyPath:@"value"
2173 // <file id="textFile" _label="" arg-set="-text-file %"/>
2174 node2 = [[NSXMLElement alloc] initWithName:@"string"];
2175 [node2 setAttributesAsDictionary:
2176 [NSDictionary dictionaryWithObjectsAndKeys:
2178 @"-text-file %", @"arg",
2180 [self makeFileSelector:node2 on:rgroup
2181 dirsOnly:NO withLabel:NO editable:NO];
2182 # endif // !USE_IPHONE
2184 // rect = [last_child(rgroup) frame];
2186 // <string id="textURL" _label="" arg-set="text-url %"/>
2187 node2 = [[NSXMLElement alloc] initWithName:@"string"];
2188 [node2 setAttributesAsDictionary:
2189 [NSDictionary dictionaryWithObjectsAndKeys:
2191 @"-text-url %", @"arg",
2193 @"URL to display", @"_label",
2196 [self makeTextField:node2 on:rgroup
2204 // rect = [last_child(rgroup) frame];
2208 // <string id="textProgram" _label="" arg-set="text-program %"/>
2209 node2 = [[NSXMLElement alloc] initWithName:@"string"];
2210 [node2 setAttributesAsDictionary:
2211 [NSDictionary dictionaryWithObjectsAndKeys:
2212 @"textProgram", @"id",
2213 @"-text-program %", @"arg",
2215 [self makeTextField:node2 on:rgroup withLabel:NO horizontal:NO];
2218 // rect = [last_child(rgroup) frame];
2220 layout_group (rgroup, NO);
2222 rect = [rgroup frame];
2223 rect.size.width += 35; // WTF? Why is rgroup too narrow?
2224 [rgroup setFrame:rect];
2227 // Set the height of the cells in the radio-box matrix to the height of
2228 // the (last of the) text fields.
2229 control = last_child (rgroup);
2230 rect = [control frame];
2231 rect.size.width = 30; // width of the string "Text", plus a bit...
2233 rect.size.width += 25;
2234 rect.size.height += LINE_SPACING;
2235 [matrix setCellSize:rect.size];
2236 [matrix sizeToCells];
2238 layout_group (group, YES);
2239 rect = [matrix frame];
2240 rect.origin.x += rect.size.width + COLUMN_SPACING;
2241 rect.origin.y -= [control frame].size.height - LINE_SPACING;
2242 [rgroup setFrameOrigin:rect.origin];
2244 // now cheat on the size of the matrix: allow it to overlap (underlap)
2247 rect.size = [matrix cellSize];
2248 rect.size.width = 300;
2249 [matrix setCellSize:rect.size];
2250 [matrix sizeToCells];
2252 // Cheat on the position of the stuff on the right (the rgroup).
2253 // GAAAH, this code is such crap!
2254 rect = [rgroup frame];
2256 [rgroup setFrame:rect];
2259 rect.size.width = rect.size.height = 0;
2260 NSBox *box = [[NSBox alloc] initWithFrame:rect];
2261 [box setTitlePosition:NSAtTop];
2262 [box setBorderType:NSBezelBorder];
2263 [box setTitle:@"Display Text"];
2265 rect.size.width = rect.size.height = 12;
2266 [box setContentViewMargins:rect.size];
2267 [box setContentView:group];
2270 [self placeChild:box on:parent];
2272 # endif // !USE_IPHONE
2276 - (void) makeImageLoaderControlBox:(NSXMLNode *)node on:(NSView *)parent
2279 [x] Grab desktop images
2280 [ ] Choose random image:
2281 [__________________________] [Choose]
2283 <boolean id="grabDesktopImages" _label="Grab desktop images"
2284 arg-unset="-no-grab-desktop"/>
2285 <boolean id="chooseRandomImages" _label="Grab desktop images"
2286 arg-unset="-choose-random-images"/>
2287 <file id="imageDirectory" _label="" arg-set="-image-directory %"/>
2290 NSXMLElement *node2;
2293 # define SCREENS "Grab desktop images"
2294 # define PHOTOS "Choose random images"
2296 # define SCREENS "Grab screenshots"
2297 # define PHOTOS "Use photo library"
2300 node2 = [[NSXMLElement alloc] initWithName:@"boolean"];
2301 [node2 setAttributesAsDictionary:
2302 [NSDictionary dictionaryWithObjectsAndKeys:
2303 @"grabDesktopImages", @"id",
2304 @ SCREENS, @"_label",
2305 @"-no-grab-desktop", @"arg-unset",
2307 [self makeCheckbox:node2 on:parent];
2309 node2 = [[NSXMLElement alloc] initWithName:@"boolean"];
2310 [node2 setAttributesAsDictionary:
2311 [NSDictionary dictionaryWithObjectsAndKeys:
2312 @"chooseRandomImages", @"id",
2313 @ PHOTOS, @"_label",
2314 @"-choose-random-images", @"arg-set",
2316 [self makeCheckbox:node2 on:parent];
2318 node2 = [[NSXMLElement alloc] initWithName:@"string"];
2319 [node2 setAttributesAsDictionary:
2320 [NSDictionary dictionaryWithObjectsAndKeys:
2321 @"imageDirectory", @"id",
2322 @"Images from:", @"_label",
2323 @"-image-directory %", @"arg",
2325 [self makeFileSelector:node2 on:parent
2326 dirsOnly:YES withLabel:YES editable:YES];
2332 // Add a second, explanatory label below the file/URL selector.
2335 lab2 = [self makeLabel:@"(Local folder, or URL of RSS or Atom feed)"];
2336 [self placeChild:lab2 on:parent];
2338 // Pack it in a little tighter vertically.
2339 NSRect r2 = [lab2 frame];
2342 [lab2 setFrameOrigin:r2.origin];
2344 # endif // USE_IPHONE
2348 #pragma mark Layout for controls
2353 last_child (NSView *parent)
2355 NSArray *kids = [parent subviews];
2356 int nkids = [kids count];
2360 return [kids objectAtIndex:nkids-1];
2362 #endif // USE_IPHONE
2365 /* Add the child as a subview of the parent, positioning it immediately
2366 below or to the right of the previously-added child of that view.
2368 - (void) placeChild:
2374 on:(NSView *)parent right:(BOOL)right_p
2377 NSRect rect = [child frame];
2378 NSView *last = last_child (parent);
2380 rect.origin.x = LEFT_MARGIN;
2381 rect.origin.y = ([parent frame].size.height - rect.size.height
2383 } else if (right_p) {
2384 rect = [last frame];
2385 rect.origin.x += rect.size.width + COLUMN_SPACING;
2387 rect = [last frame];
2388 rect.origin.x = LEFT_MARGIN;
2389 rect.origin.y -= [child frame].size.height + LINE_SPACING;
2391 NSRect r = [child frame];
2392 r.origin = rect.origin;
2394 [parent addSubview:child];
2396 # else // USE_IPHONE
2398 // Controls is an array of arrays of the controls, divided into sections.
2400 controls = [[NSMutableArray arrayWithCapacity:10] retain];
2401 if ([controls count] == 0)
2402 [controls addObject: [NSMutableArray arrayWithCapacity:10]];
2403 NSMutableArray *current = [controls objectAtIndex:[controls count]-1];
2405 if (!right_p || [current count] == 0) {
2406 // Nothing on the current line. Add this object.
2407 [current addObject: child];
2409 // Something's on the current line already.
2410 NSObject *old = [current objectAtIndex:[current count]-1];
2411 if ([old isKindOfClass:[NSMutableArray class]]) {
2412 // Already an array in this cell. Append.
2413 NSAssert ([(NSArray *) old count] < 4, @"internal error");
2414 [(NSMutableArray *) old addObject: child];
2416 // Replace the control in this cell with an array, then app
2417 NSMutableArray *a = [NSMutableArray arrayWithObjects: old, child, nil];
2418 [current replaceObjectAtIndex:[current count]-1 withObject:a];
2421 # endif // USE_IPHONE
2425 - (void) placeChild:(NSView *)child on:(NSView *)parent
2427 [self placeChild:child on:parent right:NO];
2433 // Start putting subsequent children in a new group, to create a new
2434 // section on the UITableView.
2436 - (void) placeSeparator
2438 if (! controls) return;
2439 if ([controls count] == 0) return;
2440 if ([[controls objectAtIndex:[controls count]-1]
2442 [controls addObject: [NSMutableArray arrayWithCapacity:10]];
2444 #endif // USE_IPHONE
2448 /* Creates an invisible NSBox (for layout purposes) to enclose the widgets
2449 wrapped in <hgroup> or <vgroup> in the XML.
2451 - (void) makeGroup:(NSXMLNode *)node
2453 horizontal:(BOOL) horiz_p
2456 if (!horiz_p) [self placeSeparator];
2457 [self traverseChildren:node on:parent];
2458 if (!horiz_p) [self placeSeparator];
2459 # else // !USE_IPHONE
2461 rect.size.width = rect.size.height = 1;
2462 rect.origin.x = rect.origin.y = 0;
2463 NSView *group = [[NSView alloc] initWithFrame:rect];
2464 [self traverseChildren:node on:group];
2466 layout_group (group, horiz_p);
2468 rect.size.width = rect.size.height = 0;
2469 NSBox *box = [[NSBox alloc] initWithFrame:rect];
2470 [box setTitlePosition:NSNoTitle];
2471 [box setBorderType:NSNoBorder];
2472 [box setContentViewMargins:rect.size];
2473 [box setContentView:group];
2476 [self placeChild:box on:parent];
2477 # endif // !USE_IPHONE
2483 layout_group (NSView *group, BOOL horiz_p)
2485 NSArray *kids = [group subviews];
2486 int nkids = [kids count];
2488 double maxx = 0, miny = 0;
2489 for (i = 0; i < nkids; i++) {
2490 NSView *kid = [kids objectAtIndex:i];
2491 NSRect r = [kid frame];
2494 maxx += r.size.width + COLUMN_SPACING;
2495 if (r.size.height > -miny) miny = -r.size.height;
2497 if (r.size.width > maxx) maxx = r.size.width;
2498 miny = r.origin.y - r.size.height;
2505 rect.size.width = maxx;
2506 rect.size.height = -miny;
2507 [group setFrame:rect];
2510 for (i = 0; i < nkids; i++) {
2511 NSView *kid = [kids objectAtIndex:i];
2512 NSRect r = [kid frame];
2514 r.origin.y = rect.size.height - r.size.height;
2516 x += r.size.width + COLUMN_SPACING;
2523 #endif // !USE_IPHONE
2526 /* Create some kind of control corresponding to the given XML node.
2528 -(void)makeControl:(NSXMLNode *)node on:(NSView *)parent
2530 NSString *name = [node name];
2532 if ([node kind] == NSXMLCommentKind)
2535 if ([node kind] == NSXMLTextKind) {
2536 NSString *s = [(NSString *) [node objectValue]
2537 stringByTrimmingCharactersInSet:
2538 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
2539 if (! [s isEqualToString:@""]) {
2540 NSAssert1 (0, @"unexpected text: %@", s);
2545 if ([node kind] != NSXMLElementKind) {
2546 NSAssert2 (0, @"weird XML node kind: %d: %@", (int)[node kind], node);
2550 if ([name isEqualToString:@"hgroup"] ||
2551 [name isEqualToString:@"vgroup"]) {
2553 [self makeGroup:node on:parent
2554 horizontal:[name isEqualToString:@"hgroup"]];
2556 } else if ([name isEqualToString:@"command"]) {
2557 // do nothing: this is the "-root" business
2559 } else if ([name isEqualToString:@"boolean"]) {
2560 [self makeCheckbox:node on:parent];
2562 } else if ([name isEqualToString:@"string"]) {
2563 [self makeTextField:node on:parent withLabel:NO horizontal:NO];
2565 } else if ([name isEqualToString:@"file"]) {
2566 [self makeFileSelector:node on:parent
2567 dirsOnly:NO withLabel:YES editable:NO];
2569 } else if ([name isEqualToString:@"number"]) {
2570 [self makeNumberSelector:node on:parent];
2572 } else if ([name isEqualToString:@"select"]) {
2573 [self makeOptionMenu:node on:parent];
2575 } else if ([name isEqualToString:@"_description"]) {
2576 [self makeDescLabel:node on:parent];
2578 } else if ([name isEqualToString:@"xscreensaver-text"]) {
2579 [self makeTextLoaderControlBox:node on:parent];
2581 } else if ([name isEqualToString:@"xscreensaver-image"]) {
2582 [self makeImageLoaderControlBox:node on:parent];
2585 NSAssert1 (0, @"unknown tag: %@", name);
2590 /* Iterate over and process the children of this XML node.
2592 - (void)traverseChildren:(NSXMLNode *)node on:(NSView *)parent
2594 NSArray *children = [node children];
2595 int i, count = [children count];
2596 for (i = 0; i < count; i++) {
2597 NSXMLNode *child = [children objectAtIndex:i];
2598 [self makeControl:child on:parent];
2605 /* Kludgey magic to make the window enclose the controls we created.
2608 fix_contentview_size (NSView *parent)
2611 NSArray *kids = [parent subviews];
2612 int nkids = [kids count];
2613 NSView *text = 0; // the NSText at the bottom of the window
2614 double maxx = 0, miny = 0;
2617 /* Find the size of the rectangle taken up by each of the children
2618 except the final "NSText" child.
2620 for (i = 0; i < nkids; i++) {
2621 NSView *kid = [kids objectAtIndex:i];
2622 if ([kid isKindOfClass:[NSText class]]) {
2627 if (f.origin.x + f.size.width > maxx) maxx = f.origin.x + f.size.width;
2628 if (f.origin.y - f.size.height < miny) miny = f.origin.y;
2629 // NSLog(@"start: %3.0f x %3.0f @ %3.0f %3.0f %3.0f %@",
2630 // f.size.width, f.size.height, f.origin.x, f.origin.y,
2631 // f.origin.y + f.size.height, [kid class]);
2634 if (maxx < 400) maxx = 400; // leave room for the NSText paragraph...
2636 /* Now that we know the width of the window, set the width of the NSText to
2637 that, so that it can decide what its height needs to be.
2639 if (! text) abort();
2641 // NSLog(@"text old: %3.0f x %3.0f @ %3.0f %3.0f %3.0f %@",
2642 // f.size.width, f.size.height, f.origin.x, f.origin.y,
2643 // f.origin.y + f.size.height, [text class]);
2645 // set the NSText's width (this changes its height).
2646 f.size.width = maxx - LEFT_MARGIN;
2649 // position the NSText below the last child (this gives us a new miny).
2651 f.origin.y = miny - f.size.height - LINE_SPACING;
2652 miny = f.origin.y - LINE_SPACING;
2655 // Lock the width of the field and unlock the height, and let it resize
2656 // once more, to compute the proper height of the text for that width.
2658 [(NSText *) text setHorizontallyResizable:NO];
2659 [(NSText *) text setVerticallyResizable:YES];
2660 [(NSText *) text sizeToFit];
2662 // Now lock the height too: no more resizing this text field.
2664 [(NSText *) text setVerticallyResizable:NO];
2666 // Now reposition the top edge of the text field to be back where it
2667 // was before we changed the height.
2669 float oh = f.size.height;
2671 float dh = f.size.height - oh;
2674 // #### This is needed in OSX 10.5, but is wrong in OSX 10.6. WTF??
2675 // If we do this in 10.6, the text field moves down, off the window.
2676 // So instead we repair it at the end, at the "WTF2" comment.
2679 // Also adjust the parent height by the change in height of the text field.
2682 // NSLog(@"text new: %3.0f x %3.0f @ %3.0f %3.0f %3.0f %@",
2683 // f.size.width, f.size.height, f.origin.x, f.origin.y,
2684 // f.origin.y + f.size.height, [text class]);
2687 /* Set the contentView to the size of the children.
2690 // float yoff = f.size.height;
2691 f.size.width = maxx + LEFT_MARGIN;
2692 f.size.height = -(miny - LEFT_MARGIN*2);
2693 // yoff = f.size.height - yoff;
2694 [parent setFrame:f];
2696 // NSLog(@"max: %3.0f x %3.0f @ %3.0f %3.0f",
2697 // f.size.width, f.size.height, f.origin.x, f.origin.y);
2699 /* Now move all of the kids up into the window.
2702 float shift = f.size.height;
2703 // NSLog(@"shift: %3.0f", shift);
2704 for (i = 0; i < nkids; i++) {
2705 NSView *kid = [kids objectAtIndex:i];
2707 f.origin.y += shift;
2709 // NSLog(@"move: %3.0f x %3.0f @ %3.0f %3.0f %3.0f %@",
2710 // f.size.width, f.size.height, f.origin.x, f.origin.y,
2711 // f.origin.y + f.size.height, [kid class]);
2716 parent: 420 x 541 @ 0 0
2717 text: 380 x 100 @ 20 22 miny=-501
2720 parent: 420 x 541 @ 0 0
2721 text: 380 x 100 @ 20 50 miny=-501
2724 // #### WTF2: See "WTF" above. If the text field is off the screen,
2725 // move it up. We need this on 10.6 but not on 10.5. Auugh.
2728 if (f.origin.y < 50) { // magic numbers, yay
2733 /* Set the kids to track the top left corner of the window when resized.
2734 Set the NSText to track the bottom right corner as well.
2736 for (i = 0; i < nkids; i++) {
2737 NSView *kid = [kids objectAtIndex:i];
2738 unsigned long mask = NSViewMaxXMargin | NSViewMinYMargin;
2739 if ([kid isKindOfClass:[NSText class]])
2740 mask |= NSViewWidthSizable|NSViewHeightSizable;
2741 [kid setAutoresizingMask:mask];
2744 # endif // !USE_IPHONE
2750 wrap_with_buttons (NSWindow *window, NSView *panel)
2754 // Make a box to hold the buttons at the bottom of the window.
2756 rect = [panel frame];
2757 rect.origin.x = rect.origin.y = 0;
2758 rect.size.height = 10;
2759 NSBox *bbox = [[NSBox alloc] initWithFrame:rect];
2760 [bbox setTitlePosition:NSNoTitle];
2761 [bbox setBorderType:NSNoBorder];
2763 // Make some buttons: Default, Cancel, OK
2765 rect.origin.x = rect.origin.y = 0;
2766 rect.size.width = rect.size.height = 10;
2767 NSButton *reset = [[NSButton alloc] initWithFrame:rect];
2768 [reset setTitle:@"Reset to Defaults"];
2769 [reset setBezelStyle:NSRoundedBezelStyle];
2772 rect = [reset frame];
2773 NSButton *ok = [[NSButton alloc] initWithFrame:rect];
2774 [ok setTitle:@"OK"];
2775 [ok setBezelStyle:NSRoundedBezelStyle];
2777 rect = [bbox frame];
2778 rect.origin.x = rect.size.width - [ok frame].size.width;
2779 [ok setFrameOrigin:rect.origin];
2782 NSButton *cancel = [[NSButton alloc] initWithFrame:rect];
2783 [cancel setTitle:@"Cancel"];
2784 [cancel setBezelStyle:NSRoundedBezelStyle];
2786 rect.origin.x -= [cancel frame].size.width + 10;
2787 [cancel setFrameOrigin:rect.origin];
2789 // Bind OK to RET and Cancel to ESC.
2790 [ok setKeyEquivalent:@"\r"];
2791 [cancel setKeyEquivalent:@"\e"];
2793 // The correct width for OK and Cancel buttons is 68 pixels
2794 // ("Human Interface Guidelines: Controls: Buttons:
2795 // Push Button Specifications").
2798 rect.size.width = 68;
2801 rect = [cancel frame];
2802 rect.size.width = 68;
2803 [cancel setFrame:rect];
2805 // It puts the buttons in the box or else it gets the hose again
2807 [bbox addSubview:ok];
2808 [bbox addSubview:cancel];
2809 [bbox addSubview:reset];
2812 // make a box to hold the button-box, and the preferences view
2814 rect = [bbox frame];
2815 rect.origin.y += rect.size.height;
2816 NSBox *pbox = [[NSBox alloc] initWithFrame:rect];
2817 [pbox setTitlePosition:NSNoTitle];
2818 [pbox setBorderType:NSBezelBorder];
2820 // Enforce a max height on the dialog, so that it's obvious to me
2821 // (on a big screen) when the dialog will fall off the bottom of
2822 // a small screen (e.g., 1024x768 laptop with a huge bottom dock).
2824 NSRect f = [panel frame];
2825 int screen_height = (768 // shortest "modern" Mac display
2827 - 56 // System Preferences toolbar
2828 - 140 // default magnified bottom dock icon
2830 if (f.size.height > screen_height) {
2831 NSLog(@"%@ height was %.0f; clipping to %d",
2832 [panel class], f.size.height, screen_height);
2833 f.size.height = screen_height;
2838 [pbox addSubview:panel];
2839 [pbox addSubview:bbox];
2842 [reset setAutoresizingMask:NSViewMaxXMargin];
2843 [cancel setAutoresizingMask:NSViewMinXMargin];
2844 [ok setAutoresizingMask:NSViewMinXMargin];
2845 [bbox setAutoresizingMask:NSViewWidthSizable];
2849 [ok setTarget:window];
2850 [cancel setTarget:window];
2851 [reset setTarget:window];
2852 [ok setAction:@selector(okAction:)];
2853 [cancel setAction:@selector(cancelAction:)];
2854 [reset setAction:@selector(resetAction:)];
2858 #endif // !USE_IPHONE
2861 /* Iterate over and process the children of the root node of the XML document.
2863 - (void)traverseTree
2866 NSView *parent = [self view];
2868 NSWindow *parent = self;
2870 NSXMLNode *node = xml_root;
2872 if (![[node name] isEqualToString:@"screensaver"]) {
2873 NSAssert (0, @"top level node is not <xscreensaver>");
2876 saver_name = [self parseXScreenSaverTag: node];
2877 saver_name = [saver_name stringByReplacingOccurrencesOfString:@" "
2879 [saver_name retain];
2884 rect.origin.x = rect.origin.y = 0;
2885 rect.size.width = rect.size.height = 1;
2887 NSView *panel = [[NSView alloc] initWithFrame:rect];
2888 [self traverseChildren:node on:panel];
2889 fix_contentview_size (panel);
2891 NSView *root = wrap_with_buttons (parent, panel);
2892 [userDefaultsController setAppliesImmediately:NO];
2894 [panel setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
2896 rect = [parent frameRectForContentRect:[root frame]];
2897 [parent setFrame:rect display:NO];
2898 [parent setMinSize:rect.size];
2900 [parent setContentView:root];
2902 # else // USE_IPHONE
2904 CGRect r = [parent frame];
2905 r.size = [[UIScreen mainScreen] bounds].size;
2906 [parent setFrame:r];
2907 [self traverseChildren:node on:parent];
2909 # endif // USE_IPHONE
2913 - (void)parser:(NSXMLParser *)parser
2914 didStartElement:(NSString *)elt
2915 namespaceURI:(NSString *)ns
2916 qualifiedName:(NSString *)qn
2917 attributes:(NSDictionary *)attrs
2919 NSXMLElement *e = [[NSXMLElement alloc] initWithName:elt];
2920 [e setKind:SimpleXMLElementKind];
2921 [e setAttributesAsDictionary:attrs];
2922 NSXMLElement *p = xml_parsing;
2926 xml_root = xml_parsing;
2929 - (void)parser:(NSXMLParser *)parser
2930 didEndElement:(NSString *)elt
2931 namespaceURI:(NSString *)ns
2932 qualifiedName:(NSString *)qn
2934 NSXMLElement *p = xml_parsing;
2936 NSLog(@"extra close: %@", elt);
2937 } else if (![[p name] isEqualToString:elt]) {
2938 NSLog(@"%@ closed by %@", [p name], elt);
2940 NSXMLElement *n = xml_parsing;
2941 xml_parsing = [n parent];
2946 - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
2948 NSXMLElement *e = [[NSXMLElement alloc] initWithName:@"text"];
2949 [e setKind:SimpleXMLTextKind];
2950 NSXMLElement *p = xml_parsing;
2952 [e setObjectValue: string];
2957 # ifdef USE_PICKER_VIEW
2959 #pragma mark UIPickerView delegate methods
2961 - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pv
2963 return 1; // Columns
2966 - (NSInteger)pickerView:(UIPickerView *)pv
2967 numberOfRowsInComponent:(NSInteger)column
2969 NSAssert (column == 0, @"weird column");
2970 NSArray *a = [picker_values objectAtIndex: [pv tag]];
2971 if (! a) return 0; // Too early?
2975 - (CGFloat)pickerView:(UIPickerView *)pv
2976 rowHeightForComponent:(NSInteger)column
2981 - (CGFloat)pickerView:(UIPickerView *)pv
2982 widthForComponent:(NSInteger)column
2984 NSAssert (column == 0, @"weird column");
2985 NSArray *a = [picker_values objectAtIndex: [pv tag]];
2986 if (! a) return 0; // Too early?
2988 UIFont *f = [UIFont systemFontOfSize:[NSFont systemFontSize]];
2990 for (NSArray *a2 in a) {
2991 NSString *s = [a2 objectAtIndex:0];
2992 CGSize r = [s sizeWithFont:f];
2993 if (r.width > max) max = r.width;
2996 max *= 1.7; // WTF!!
3008 - (NSString *)pickerView:(UIPickerView *)pv
3009 titleForRow:(NSInteger)row
3010 forComponent:(NSInteger)column
3012 NSAssert (column == 0, @"weird column");
3013 NSArray *a = [picker_values objectAtIndex: [pv tag]];
3014 if (! a) return 0; // Too early?
3015 a = [a objectAtIndex:row];
3016 NSAssert (a, @"internal error");
3017 return [a objectAtIndex:0];
3020 # endif // USE_PICKER_VIEW
3023 #pragma mark UITableView delegate methods
3025 - (void) addResetButton
3027 [[self navigationItem]
3028 setRightBarButtonItem: [[UIBarButtonItem alloc]
3029 initWithTitle: @"Reset to Defaults"
3030 style: UIBarButtonItemStyleBordered
3032 action:@selector(resetAction:)]];
3033 NSString *s = saver_name;
3034 if ([self view].frame.size.width > 320)
3035 s = [s stringByAppendingString: @" Settings"];
3036 [self navigationItem].title = s;
3040 - (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)o
3045 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tv {
3046 // Number of vertically-stacked white boxes.
3047 return [controls count];
3050 - (NSInteger)tableView:(UITableView *)tableView
3051 numberOfRowsInSection:(NSInteger)section
3053 // Number of lines in each vertically-stacked white box.
3054 NSAssert (controls, @"internal error");
3055 return [[controls objectAtIndex:section] count];
3058 - (NSString *)tableView:(UITableView *)tv
3059 titleForHeaderInSection:(NSInteger)section
3061 // Titles above each vertically-stacked white box.
3062 // if (section == 0)
3063 // return [saver_name stringByAppendingString:@" Settings"];
3068 - (CGFloat)tableView:(UITableView *)tv
3069 heightForRowAtIndexPath:(NSIndexPath *)ip
3071 CGFloat h = [tv rowHeight];
3073 NSView *ctl = [[controls objectAtIndex:[ip indexAtPosition:0]]
3074 objectAtIndex:[ip indexAtPosition:1]];
3076 if ([ctl isKindOfClass:[NSArray class]]) {
3077 NSArray *set = (NSArray *) ctl;
3078 switch ([set count]) {
3080 # ifdef LABEL_ABOVE_SLIDER
3081 h *= 1.7; break; // label + left/slider/right: 2 1/2 lines
3083 case 3: h *= 1.2; break; // left/slider/right: 1 1/2 lines
3085 if ([[set objectAtIndex:1] isKindOfClass:[UITextField class]])
3089 } else if ([ctl isKindOfClass:[UILabel class]]) {
3090 UILabel *t = (UILabel *) ctl;
3092 r.size.width = 250; // WTF! Black magic!
3093 r.size.width -= LEFT_MARGIN;
3097 h = r.size.height + LINE_SPACING * 3;
3098 # ifdef USE_HTML_LABELS
3100 } else if ([ctl isKindOfClass:[HTMLLabel class]]) {
3102 HTMLLabel *t = (HTMLLabel *) ctl;
3104 r.size.width = [tv frame].size.width;
3105 r.size.width -= LEFT_MARGIN * 2;
3109 h = r.size.height + LINE_SPACING * 3;
3111 # endif // USE_HTML_LABELS
3113 CGFloat h2 = [ctl frame].size.height;
3114 h2 += LINE_SPACING * 2;
3122 - (void)refreshTableView
3124 UITableView *tv = (UITableView *) [self view];
3125 NSMutableArray *a = [NSMutableArray arrayWithCapacity:20];
3126 int rows = [self numberOfSectionsInTableView:tv];
3127 for (int i = 0; i < rows; i++) {
3128 int cols = [self tableView:tv numberOfRowsInSection:i];
3129 for (int j = 0; j < cols; j++) {
3133 [a addObject: [NSIndexPath indexPathWithIndexes:ip length:2]];
3138 [tv reloadRowsAtIndexPaths:a withRowAnimation:UITableViewRowAnimationNone];
3143 - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)o
3145 [NSTimer scheduledTimerWithTimeInterval: 0
3147 selector:@selector(refreshTableView)
3153 #ifndef USE_PICKER_VIEW
3155 - (void)updateRadioGroupCell:(UITableViewCell *)cell
3156 button:(RadioButton *)b
3158 NSArray *item = [[b items] objectAtIndex: [b index]];
3159 NSString *pref_key = [item objectAtIndex:1];
3160 NSObject *pref_val = [item objectAtIndex:2];
3161 NSObject *current = [userDefaultsController objectForKey:pref_key];
3163 // Convert them both to strings and compare those, so that
3164 // we don't get screwed by int 1 versus string "1".
3165 // Will boolean true/1 screw us here too?
3167 NSString *pref_str = ([pref_val isKindOfClass:[NSString class]]
3168 ? (NSString *) pref_val
3169 : [(NSNumber *) pref_val stringValue]);
3170 NSString *current_str = ([current isKindOfClass:[NSString class]]
3171 ? (NSString *) current
3172 : [(NSNumber *) current stringValue]);
3173 BOOL match_p = [current_str isEqualToString:pref_str];
3175 // NSLog(@"\"%@\" = \"%@\" | \"%@\" ", pref_key, pref_val, current_str);
3178 [cell setAccessoryType:UITableViewCellAccessoryCheckmark];
3180 [cell setAccessoryType:UITableViewCellAccessoryNone];
3184 - (void)tableView:(UITableView *)tv
3185 didSelectRowAtIndexPath:(NSIndexPath *)ip
3187 RadioButton *ctl = [[controls objectAtIndex:[ip indexAtPosition:0]]
3188 objectAtIndex:[ip indexAtPosition:1]];
3189 if (! [ctl isKindOfClass:[RadioButton class]])
3192 [self radioAction:ctl];
3193 [self refreshTableView];
3197 #endif // !USE_PICKER_VIEW
3201 - (UITableViewCell *)tableView:(UITableView *)tv
3202 cellForRowAtIndexPath:(NSIndexPath *)ip
3205 /* #### If we re-use cells, then clicking on a checkbox RadioButton
3206 (in non-USE_PICKER_VIEW mode) makes all the cells disappear.
3207 This doesn't happen if we don't re-use any cells. Oh well.
3209 NSString *id = [NSString stringWithFormat: @"%d:%d",
3210 [ip indexAtPosition:0],
3211 [ip indexAtPosition:1]];
3212 UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier: id];
3214 if (cell) return cell;
3217 UITableViewCell *cell;
3220 cell = [[[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault
3221 reuseIdentifier: id]
3223 cell.selectionStyle = UITableViewCellSelectionStyleNone;
3225 CGRect p = [cell frame];
3228 p.size.height = [self tableView:tv heightForRowAtIndexPath:ip];
3231 // Allocate more space to the controls on iPad screens,
3232 // and on landscape-mode iPhones.
3233 CGFloat ww = [tv frame].size.width;
3234 CGFloat left_edge = (ww > 700
3235 ? p.size.width * 0.9
3237 ? p.size.width * 0.5
3238 : p.size.width * 0.3);
3239 CGFloat right_edge = p.origin.x + p.size.width - LEFT_MARGIN;
3242 NSView *ctl = [[controls objectAtIndex:[ip indexAtPosition:0]]
3243 objectAtIndex:[ip indexAtPosition:1]];
3245 if ([ctl isKindOfClass:[NSArray class]]) {
3246 // This cell has a set of objects in it.
3247 NSArray *set = (NSArray *) ctl;
3248 switch ([set count]) {
3251 // With 2 elements, the first of the pair must be a label.
3252 UILabel *label = (UILabel *) [set objectAtIndex: 0];
3253 NSAssert ([label isKindOfClass:[UILabel class]], @"unhandled type");
3254 ctl = [set objectAtIndex: 1];
3257 if ([ctl isKindOfClass:[UISwitch class]]) {
3258 // Flush right checkboxes.
3259 ctl.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
3260 r.size.width = 80; // Magic.
3261 r.origin.x = right_edge - r.size.width;
3263 // Expandable sliders.
3264 ctl.autoresizingMask = UIViewAutoresizingFlexibleWidth;
3265 r.origin.x = left_edge;
3266 r.size.width = right_edge - r.origin.x;
3268 r.origin.y = (p.size.height - r.size.height) / 2;
3272 NSView *box = [[UIView alloc] initWithFrame:p];
3273 [box addSubview: ctl];
3275 // cell.textLabel.text = [(UILabel *) ctl text];
3277 r.origin.x = LEFT_MARGIN;
3279 r.size.width = [ctl frame].origin.x - r.origin.x;
3280 r.size.height = p.size.height;
3282 [label setFont:[NSFont boldSystemFontOfSize: FONT_SIZE]];
3283 label.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
3284 box. autoresizingMask = UIViewAutoresizingFlexibleWidth;
3285 [box addSubview: label];
3293 // With 3 elements, the first and last must be labels.
3294 // With 4 elements, the first, second and last must be labels.
3296 UILabel *top = ([set count] == 4
3297 ? [set objectAtIndex: i++]
3299 UILabel *left = [set objectAtIndex: i++];
3300 NSView *mid = [set objectAtIndex: i++];
3301 UILabel *right = [set objectAtIndex: i++];
3302 NSAssert (!top || [top isKindOfClass:[UILabel class]], @"WTF");
3303 NSAssert ( [left isKindOfClass:[UILabel class]], @"WTF");
3304 NSAssert ( ![mid isKindOfClass:[UILabel class]], @"WTF");
3305 NSAssert ( [right isKindOfClass:[UILabel class]], @"WTF");
3307 // 3 elements: control at top of cell.
3308 // 4 elements: center the control vertically.
3310 # ifdef LABEL_ABOVE_SLIDER
3311 left_edge = LEFT_MARGIN;
3313 r.origin.x = left_edge;
3314 r.size.width = right_edge - r.origin.x;
3315 r.origin.y = ([set count] == 3
3317 : (p.size.height - r.size.height) / 2);
3320 // Top label goes above, flush center/top.
3322 r.size = [[top text] sizeWithFont:[top font]
3324 CGSizeMake (p.size.width - LEFT_MARGIN*2,
3326 lineBreakMode:[top lineBreakMode]];
3327 r.origin.x = (p.size.width - r.size.width) / 2;
3332 // Left label goes under control, flush left/bottom.
3333 r.size = [[left text] sizeWithFont:[left font]
3335 CGSizeMake(p.size.width - LEFT_MARGIN*2,
3337 lineBreakMode:[left lineBreakMode]];
3338 r.origin.x = [mid frame].origin.x;
3339 r.origin.y = p.size.height - r.size.height - 4;
3342 // Right label goes under control, flush right/bottom.
3344 r.size = [[right text] sizeWithFont:[right font]
3346 CGSizeMake(p.size.width - LEFT_MARGIN*2,
3348 lineBreakMode:[right lineBreakMode]];
3349 r.origin.x = ([mid frame].origin.x + [mid frame].size.width -
3351 r.origin.y = [left frame].origin.y;
3355 ctl = [[UIView alloc] initWithFrame:p];
3357 # ifdef LABEL_ABOVE_SLIDER
3358 [ctl addSubview: top];
3359 top.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin|
3360 UIViewAutoresizingFlexibleRightMargin);
3363 r.origin.x = LEFT_MARGIN;
3365 r.size.width = [mid frame].origin.x - r.origin.x;
3366 r.size.height = p.size.height;
3368 top.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
3369 [ctl addSubview: top];
3372 [ctl addSubview: left];
3373 [ctl addSubview: mid];
3374 [ctl addSubview: right];
3376 left. autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
3377 mid. autoresizingMask = UIViewAutoresizingFlexibleWidth;
3378 right.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
3379 ctl. autoresizingMask = UIViewAutoresizingFlexibleWidth;
3383 NSAssert (0, @"unhandled size");
3386 // A single view, not a pair.
3389 r.origin.x = LEFT_MARGIN;
3390 r.origin.y = LINE_SPACING;
3391 r.size.width = right_edge - r.origin.x;
3394 ctl.autoresizingMask = UIViewAutoresizingFlexibleWidth;
3396 # ifndef USE_PICKER_VIEW
3397 if ([ctl isKindOfClass:[RadioButton class]])
3398 [self updateRadioGroupCell:cell button:(RadioButton *)ctl];
3399 # endif // USE_PICKER_VIEW
3402 if ([ctl isKindOfClass:[UILabel class]]) {
3403 // Make label full height to allow text to line-wrap if necessary.
3405 r.origin.y = p.origin.y;
3406 r.size.height = p.size.height;
3410 [cell.contentView addSubview: ctl];
3414 # endif // USE_IPHONE
3417 /* When this object is instantiated, it parses the XML file and creates
3418 controls on itself that are hooked up to the appropriate preferences.
3419 The default size of the view is just big enough to hold them all.
3421 - (id)initWithXML: (NSData *) xml_data
3422 options: (const XrmOptionDescRec *) _opts
3423 controller: (NSUserDefaultsController *) _prefs
3424 defaults: (NSDictionary *) _defs
3427 self = [super init];
3428 # else // !USE_IPHONE
3429 self = [super initWithStyle:UITableViewStyleGrouped];
3430 self.title = [saver_name stringByAppendingString:@" Settings"];
3431 # endif // !USE_IPHONE
3432 if (! self) return 0;
3434 // instance variables
3436 defaultOptions = _defs;
3437 userDefaultsController = _prefs;
3438 [userDefaultsController retain];
3440 NSXMLParser *xmlDoc = [[NSXMLParser alloc] initWithData:xml_data];
3443 NSAssert1 (0, @"XML Error: %@", xml_data);
3446 [xmlDoc setDelegate:self];
3447 if (! [xmlDoc parse]) {
3448 NSError *err = [xmlDoc parserError];
3449 NSAssert2 (0, @"XML Error: %@: %@", xml_data, err);
3453 [self traverseTree];
3457 [self addResetButton];
3466 [saver_name release];
3467 [userDefaultsController release];
3470 [pref_keys release];
3471 [pref_ctls release];
3472 # ifdef USE_PICKER_VIEW
3473 [picker_values release];