1 /* xscreensaver, Copyright (c) 2006-2012 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: UILineBreakModeWordWrap];
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]];
536 double v = [sender value];
538 [userDefaultsController setInteger:v forKey:pref_key];
540 [userDefaultsController setDouble:v forKey:pref_key];
543 // Called when a checkbox/switch is bonked.
545 - (void)switchAction:(UISwitch*)sender
547 if ([active_text_field canResignFirstResponder])
548 [active_text_field resignFirstResponder];
549 NSString *pref_key = [pref_keys objectAtIndex: [sender tag]];
550 NSString *v = ([sender isOn] ? @"true" : @"false");
551 [userDefaultsController setObject:v forKey:pref_key];
554 # ifdef USE_PICKER_VIEW
555 // Called when a picker is bonked.
557 - (void)pickerView:(UIPickerView *)pv
558 didSelectRow:(NSInteger)row
559 inComponent:(NSInteger)column
561 if ([active_text_field canResignFirstResponder])
562 [active_text_field resignFirstResponder];
564 NSAssert (column == 0, @"internal error");
565 NSArray *a = [picker_values objectAtIndex: [pv tag]];
566 if (! a) return; // Too early?
567 a = [a objectAtIndex:row];
568 NSAssert (a, @"missing row");
570 //NSString *label = [a objectAtIndex:0];
571 NSString *pref_key = [a objectAtIndex:1];
572 NSObject *pref_val = [a objectAtIndex:2];
573 [userDefaultsController setObject:pref_val forKey:pref_key];
575 # else // !USE_PICKER_VIEW
577 // Called when a RadioButton is bonked.
579 - (void)radioAction:(RadioButton*)sender
581 if ([active_text_field canResignFirstResponder])
582 [active_text_field resignFirstResponder];
584 NSArray *item = [[sender items] objectAtIndex: [sender index]];
585 NSString *pref_key = [item objectAtIndex:1];
586 NSObject *pref_val = [item objectAtIndex:2];
587 [userDefaultsController setObject:pref_val forKey:pref_key];
590 - (BOOL)textFieldShouldBeginEditing:(UITextField *)tf
592 active_text_field = tf;
596 - (void)textFieldDidEndEditing:(UITextField *)tf
598 NSString *pref_key = [pref_keys objectAtIndex: [tf tag]];
599 NSString *txt = [tf text];
600 [userDefaultsController setObject:txt forKey:pref_key];
603 - (BOOL)textFieldShouldReturn:(UITextField *)tf
605 active_text_field = nil;
606 [tf resignFirstResponder];
610 # endif // !USE_PICKER_VIEW
617 - (void) okAction:(NSObject *)arg
619 [userDefaultsController commitEditing];
620 [userDefaultsController save:self];
621 [NSApp endSheet:self returnCode:NSOKButton];
625 - (void) cancelAction:(NSObject *)arg
627 [userDefaultsController revert:self];
628 [NSApp endSheet:self returnCode:NSCancelButton];
631 # endif // !USE_IPHONE
634 - (void) resetAction:(NSObject *)arg
637 [userDefaultsController revertToInitialValues:self];
640 for (NSString *key in defaultOptions) {
641 NSObject *val = [defaultOptions objectForKey:key];
642 [userDefaultsController setObject:val forKey:key];
645 for (UIControl *ctl in pref_ctls) {
646 NSString *pref_key = [pref_keys objectAtIndex: ctl.tag];
647 [self bindResource:ctl key:pref_key reload:YES];
650 [self refreshTableView];
651 # endif // USE_IPHONE
655 /* Connects a control (checkbox, etc) to the corresponding preferences key.
657 - (void) bindResource:(NSObject *)control key:(NSString *)pref_key
658 reload:(BOOL)reload_p
661 NSString *bindto = ([control isKindOfClass:[NSPopUpButton class]]
663 : ([control isKindOfClass:[NSMatrix class]]
667 toObject:userDefaultsController
668 withKeyPath:[@"values." stringByAppendingString: pref_key]
672 NSObject *val = [userDefaultsController objectForKey:pref_key];
676 if ([val isKindOfClass:[NSString class]]) {
677 sval = (NSString *) val;
678 if (NSOrderedSame == [sval caseInsensitiveCompare:@"true"] ||
679 NSOrderedSame == [sval caseInsensitiveCompare:@"yes"] ||
680 NSOrderedSame == [sval caseInsensitiveCompare:@"1"])
683 dval = [sval doubleValue];
684 } else if ([val isKindOfClass:[NSNumber class]]) {
685 // NSBoolean (__NSCFBoolean) is really NSNumber.
686 dval = [(NSNumber *) val doubleValue];
687 sval = [(NSNumber *) val stringValue];
690 if ([control isKindOfClass:[UISlider class]]) {
691 sel = @selector(sliderAction:);
692 [(UISlider *) control setValue: dval];
693 } else if ([control isKindOfClass:[UISwitch class]]) {
694 sel = @selector(switchAction:);
695 [(UISwitch *) control setOn: ((int) dval != 0)];
696 # ifdef USE_PICKER_VIEW
697 } else if ([control isKindOfClass:[UIPickerView class]]) {
699 [(UIPickerView *) control selectRow:((int)dval) inComponent:0
701 # else // !USE_PICKER_VIEW
702 } else if ([control isKindOfClass:[RadioButton class]]) {
703 sel = 0; // radioAction: sent from didSelectRowAtIndexPath.
704 } else if ([control isKindOfClass:[UITextField class]]) {
706 [(UITextField *) control setText: sval];
707 # endif // !USE_PICKER_VIEW
709 NSAssert (0, @"unknown class");
712 // NSLog(@"\"%@\" = \"%@\" [%@, %.1f]", pref_key, val, [val class], dval);
716 pref_keys = [[NSMutableArray arrayWithCapacity:10] retain];
717 pref_ctls = [[NSMutableArray arrayWithCapacity:10] retain];
720 [pref_keys addObject: [self makeKey:pref_key]];
721 [pref_ctls addObject: control];
722 ((UIControl *) control).tag = [pref_keys count] - 1;
725 [(UIControl *) control addTarget:self action:sel
726 forControlEvents:UIControlEventValueChanged];
730 # endif // USE_IPHONE
733 NSObject *def = [[userDefaultsController defaults] objectForKey:pref_key];
734 NSString *s = [NSString stringWithFormat:@"bind: \"%@\"", pref_key];
735 s = [s stringByPaddingToLength:18 withString:@" " startingAtIndex:0];
736 s = [NSString stringWithFormat:@"%@ = \"%@\"", s, def];
737 s = [s stringByPaddingToLength:28 withString:@" " startingAtIndex:0];
738 NSLog (@"%@ %@/%@", s, [def class], [control class]);
743 - (void) bindResource:(NSObject *)control key:(NSString *)pref_key
745 [self bindResource:(NSObject *)control key:(NSString *)pref_key reload:NO];
750 - (void) bindSwitch:(NSObject *)control
751 cmdline:(NSString *)cmd
753 [self bindResource:control
754 key:[self switchToResource:cmd opts:opts valRet:0]];
758 #pragma mark Text-manipulating utilities
762 unwrap (NSString *text)
764 // Unwrap lines: delete \n but do not delete \n\n.
766 NSArray *lines = [text componentsSeparatedByString:@"\n"];
767 int nlines = [lines count];
771 text = @"\n"; // start with one blank line
773 // skip trailing blank lines in file
774 for (i = nlines-1; i > 0; i--) {
775 NSString *s = (NSString *) [lines objectAtIndex:i];
781 // skip leading blank lines in file
782 for (i = 0; i < nlines; i++) {
783 NSString *s = (NSString *) [lines objectAtIndex:i];
790 for (; i < nlines; i++) {
791 NSString *s = (NSString *) [lines objectAtIndex:i];
792 if ([s length] == 0) {
793 text = [text stringByAppendingString:@"\n\n"];
795 } else if ([s characterAtIndex:0] == ' ' ||
796 [s hasPrefix:@"Copyright "] ||
797 [s hasPrefix:@"http://"]) {
798 // don't unwrap if the following line begins with whitespace,
799 // or with the word "Copyright", or if it begins with a URL.
801 text = [text stringByAppendingString:@"\n"];
802 text = [text stringByAppendingString:s];
807 text = [text stringByAppendingString:@" "];
808 text = [text stringByAppendingString:s];
819 /* Makes the text up to the first comma be bold.
822 boldify (NSText *nstext)
824 NSString *text = [nstext string];
825 NSRange r = [text rangeOfString:@"," options:0];
826 r.length = r.location+1;
830 NSFont *font = [nstext font];
831 font = [NSFont boldSystemFontOfSize:[font pointSize]];
832 [nstext setFont:font range:r];
834 # endif // !USE_IPHONE
837 /* Creates a human-readable anchor to put on a URL.
840 anchorize (const char *url)
842 const char *wiki = "http://en.wikipedia.org/wiki/";
843 const char *math = "http://mathworld.wolfram.com/";
844 if (!strncmp (wiki, url, strlen(wiki))) {
845 char *anchor = (char *) malloc (strlen(url) * 3 + 10);
846 strcpy (anchor, "Wikipedia: \"");
847 const char *in = url + strlen(wiki);
848 char *out = anchor + strlen(anchor);
852 } else if (*in == '#') {
855 } else if (*in == '%') {
861 sscanf (hex, "%x", &n);
873 } else if (!strncmp (math, url, strlen(math))) {
874 char *anchor = (char *) malloc (strlen(url) * 3 + 10);
875 strcpy (anchor, "MathWorld: \"");
876 const char *start = url + strlen(wiki);
877 const char *in = start;
878 char *out = anchor + strlen(anchor);
882 } else if (in != start && *in >= 'A' && *in <= 'Z') {
885 } else if (!strncmp (in, ".htm", 4)) {
902 #if !defined(USE_IPHONE) || !defined(USE_HTML_LABELS)
904 /* Converts any http: URLs in the given text field to clickable links.
907 hreffify (NSText *nstext)
910 NSString *text = [nstext string];
911 [nstext setRichText:YES];
913 NSString *text = [nstext text];
916 int L = [text length];
917 NSRange start; // range is start-of-search to end-of-string
920 while (start.location < L) {
922 // Find the beginning of a URL...
924 NSRange r2 = [text rangeOfString:@"http://" options:0 range:start];
925 if (r2.location == NSNotFound)
928 // Next time around, start searching after this.
929 start.location = r2.location + r2.length;
930 start.length = L - start.location;
932 // Find the end of a URL (whitespace or EOF)...
934 NSRange r3 = [text rangeOfCharacterFromSet:
935 [NSCharacterSet whitespaceAndNewlineCharacterSet]
936 options:0 range:start];
937 if (r3.location == NSNotFound) // EOF
938 r3.location = L, r3.length = 0;
940 // Next time around, start searching after this.
941 start.location = r3.location;
942 start.length = L - start.location;
944 // Set r2 to the start/length of this URL.
945 r2.length = start.location - r2.location;
948 NSString *nsurl = [text substringWithRange:r2];
949 const char *url = [nsurl UTF8String];
951 // If this is a Wikipedia URL, make the linked text be prettier.
953 char *anchor = anchorize(url);
957 // Construct the RTF corresponding to <A HREF="url">anchor</A>
959 const char *fmt = "{\\field{\\*\\fldinst{HYPERLINK \"%s\"}}%s}";
960 char *rtf = malloc (strlen (fmt) + strlen(url) + strlen(anchor) + 10);
961 sprintf (rtf, fmt, url, anchor);
963 NSData *rtfdata = [NSData dataWithBytesNoCopy:rtf length:strlen(rtf)];
964 [nstext replaceCharactersInRange:r2 withRTF:rtfdata];
966 # else // !USE_IPHONE
967 // *anchor = 0; // Omit Wikipedia anchor
968 text = [text stringByReplacingCharactersInRange:r2
969 withString:[NSString stringWithCString:anchor
970 encoding:NSUTF8StringEncoding]];
971 // text = [text stringByReplacingOccurrencesOfString:@"\n\n\n"
972 // withString:@"\n\n"];
973 # endif // !USE_IPHONE
977 int L2 = [text length]; // might have changed
978 start.location -= (L - L2);
983 [nstext setText:text];
988 #endif /* !USE_IPHONE || !USE_HTML_LABELS */
992 #pragma mark Creating controls from XML
995 /* Parse the attributes of an XML tag into a dictionary.
996 For input, the dictionary should have as attributes the keys, each
997 with @"" as their value.
998 On output, the dictionary will set the keys to the values specified,
999 and keys that were not specified will not be present in the dictionary.
1000 Warnings are printed if there are duplicate or unknown attributes.
1002 - (void) parseAttrs:(NSMutableDictionary *)dict node:(NSXMLNode *)node
1004 NSArray *attrs = [(NSXMLElement *) node attributes];
1005 int n = [attrs count];
1008 // For each key in the dictionary, fill in the dict with the corresponding
1009 // value. The value @"" is assumed to mean "un-set". Issue a warning if
1010 // an attribute is specified twice.
1012 for (i = 0; i < n; i++) {
1013 NSXMLNode *attr = [attrs objectAtIndex:i];
1014 NSString *key = [attr name];
1015 NSString *val = [attr objectValue];
1016 NSString *old = [dict objectForKey:key];
1019 NSAssert2 (0, @"unknown attribute \"%@\" in \"%@\"", key, [node name]);
1020 } else if ([old length] != 0) {
1021 NSAssert3 (0, @"duplicate %@: \"%@\", \"%@\"", key, old, val);
1023 [dict setValue:val forKey:key];
1027 // Remove from the dictionary any keys whose value is still @"",
1028 // meaning there was no such attribute specified.
1030 NSArray *keys = [dict allKeys];
1032 for (i = 0; i < n; i++) {
1033 NSString *key = [keys objectAtIndex:i];
1034 NSString *val = [dict objectForKey:key];
1035 if ([val length] == 0)
1036 [dict removeObjectForKey:key];
1040 // Kludge for starwars.xml:
1041 // If there is a "_low-label" and no "_label", but "_low-label" contains
1042 // spaces, divide them.
1043 NSString *lab = [dict objectForKey:@"_label"];
1044 NSString *low = [dict objectForKey:@"_low-label"];
1047 [[[low stringByTrimmingCharactersInSet:
1048 [NSCharacterSet whitespaceAndNewlineCharacterSet]]
1049 componentsSeparatedByString: @" "]
1050 filteredArrayUsingPredicate:
1051 [NSPredicate predicateWithFormat:@"length > 0"]];
1052 if (split && [split count] == 2) {
1053 [dict setValue:[split objectAtIndex:0] forKey:@"_label"];
1054 [dict setValue:[split objectAtIndex:1] forKey:@"_low-label"];
1057 # endif // USE_IPHONE
1061 /* Handle the options on the top level <xscreensaver> tag.
1063 - (NSString *) parseXScreenSaverTag:(NSXMLNode *)node
1065 NSMutableDictionary *dict =
1066 [NSMutableDictionary dictionaryWithObjectsAndKeys:
1071 [self parseAttrs:dict node:node];
1072 NSString *name = [dict objectForKey:@"name"];
1073 NSString *label = [dict objectForKey:@"_label"];
1075 NSAssert1 (label, @"no _label in %@", [node name]);
1076 NSAssert1 (name, @"no name in \"%@\"", label);
1081 /* Creates a label: an un-editable NSTextField displaying the given text.
1083 - (LABEL *) makeLabel:(NSString *)text
1086 rect.origin.x = rect.origin.y = 0;
1087 rect.size.width = rect.size.height = 10;
1089 NSTextField *lab = [[NSTextField alloc] initWithFrame:rect];
1090 [lab setSelectable:NO];
1091 [lab setEditable:NO];
1092 [lab setBezeled:NO];
1093 [lab setDrawsBackground:NO];
1094 [lab setStringValue:text];
1096 # else // USE_IPHONE
1097 UILabel *lab = [[UILabel alloc] initWithFrame:rect];
1098 [lab setText: [text stringByTrimmingCharactersInSet:
1099 [NSCharacterSet whitespaceAndNewlineCharacterSet]]];
1100 [lab setBackgroundColor:[UIColor clearColor]];
1101 [lab setNumberOfLines:0]; // unlimited
1102 // [lab setLineBreakMode:UILineBreakModeWordWrap];
1103 [lab setLineBreakMode:UILineBreakModeHeadTruncation];
1104 [lab setAutoresizingMask: (UIViewAutoresizingFlexibleWidth |
1105 UIViewAutoresizingFlexibleHeight)];
1106 # endif // USE_IPHONE
1111 /* Creates the checkbox (NSButton) described by the given XML node.
1113 - (void) makeCheckbox:(NSXMLNode *)node on:(NSView *)parent
1115 NSMutableDictionary *dict =
1116 [NSMutableDictionary dictionaryWithObjectsAndKeys:
1122 [self parseAttrs:dict node:node];
1123 NSString *label = [dict objectForKey:@"_label"];
1124 NSString *arg_set = [dict objectForKey:@"arg-set"];
1125 NSString *arg_unset = [dict objectForKey:@"arg-unset"];
1128 NSAssert1 (0, @"no _label in %@", [node name]);
1131 if (!arg_set && !arg_unset) {
1132 NSAssert1 (0, @"neither arg-set nor arg-unset provided in \"%@\"",
1135 if (arg_set && arg_unset) {
1136 NSAssert1 (0, @"only one of arg-set and arg-unset may be used in \"%@\"",
1140 // sanity-check the choice of argument names.
1142 if (arg_set && ([arg_set hasPrefix:@"-no-"] ||
1143 [arg_set hasPrefix:@"--no-"]))
1144 NSLog (@"arg-set should not be a \"no\" option in \"%@\": %@",
1146 if (arg_unset && (![arg_unset hasPrefix:@"-no-"] &&
1147 ![arg_unset hasPrefix:@"--no-"]))
1148 NSLog(@"arg-unset should be a \"no\" option in \"%@\": %@",
1152 rect.origin.x = rect.origin.y = 0;
1153 rect.size.width = rect.size.height = 10;
1157 NSButton *button = [[NSButton alloc] initWithFrame:rect];
1158 [button setButtonType:NSSwitchButton];
1159 [button setTitle:label];
1161 [self placeChild:button on:parent];
1163 # else // USE_IPHONE
1165 LABEL *lab = [self makeLabel:label];
1166 [self placeChild:lab on:parent];
1167 UISwitch *button = [[UISwitch alloc] initWithFrame:rect];
1168 [self placeChild:button on:parent right:YES];
1171 # endif // USE_IPHONE
1173 [self bindSwitch:button cmdline:(arg_set ? arg_set : arg_unset)];
1178 /* Creates the number selection control described by the given XML node.
1179 If "type=slider", it's an NSSlider.
1180 If "type=spinbutton", it's a text field with up/down arrows next to it.
1182 - (void) makeNumberSelector:(NSXMLNode *)node on:(NSView *)parent
1184 NSMutableDictionary *dict =
1185 [NSMutableDictionary dictionaryWithObjectsAndKeys:
1189 @"", @"_high-label",
1197 [self parseAttrs:dict node:node];
1198 NSString *label = [dict objectForKey:@"_label"];
1199 NSString *low_label = [dict objectForKey:@"_low-label"];
1200 NSString *high_label = [dict objectForKey:@"_high-label"];
1201 NSString *type = [dict objectForKey:@"type"];
1202 NSString *arg = [dict objectForKey:@"arg"];
1203 NSString *low = [dict objectForKey:@"low"];
1204 NSString *high = [dict objectForKey:@"high"];
1205 NSString *def = [dict objectForKey:@"default"];
1206 NSString *cvt = [dict objectForKey:@"convert"];
1208 NSAssert1 (arg, @"no arg in %@", label);
1209 NSAssert1 (type, @"no type in %@", label);
1212 NSAssert1 (0, @"no low in %@", [node name]);
1216 NSAssert1 (0, @"no high in %@", [node name]);
1220 NSAssert1 (0, @"no default in %@", [node name]);
1223 if (cvt && ![cvt isEqualToString:@"invert"]) {
1224 NSAssert1 (0, @"if provided, \"convert\" must be \"invert\" in %@",
1228 // If either the min or max field contains a decimal point, then this
1229 // option may have a floating point value; otherwise, it is constrained
1230 // to be an integer.
1232 NSCharacterSet *dot =
1233 [NSCharacterSet characterSetWithCharactersInString:@"."];
1234 BOOL float_p = ([low rangeOfCharacterFromSet:dot].location != NSNotFound ||
1235 [high rangeOfCharacterFromSet:dot].location != NSNotFound);
1237 if ([type isEqualToString:@"slider"]
1238 # ifdef USE_IPHONE // On iPhone, we use sliders for all numeric values.
1239 || [type isEqualToString:@"spinbutton"]
1244 rect.origin.x = rect.origin.y = 0;
1245 rect.size.width = 150;
1246 rect.size.height = 23; // apparent min height for slider with ticks...
1248 slider = [[InvertedSlider alloc] initWithFrame:rect
1250 integers: !float_p];
1251 [slider setMaxValue:[high doubleValue]];
1252 [slider setMinValue:[low doubleValue]];
1254 int range = [slider maxValue] - [slider minValue] + 1;
1257 while (range2 > max_ticks)
1260 // If we have elided ticks, leave it at the max number of ticks.
1261 if (range != range2 && range2 < max_ticks)
1264 // If it's a float, always display the max number of ticks.
1265 if (float_p && range2 < max_ticks)
1269 [slider setNumberOfTickMarks:range2];
1271 [slider setAllowsTickMarkValuesOnly:
1272 (range == range2 && // we are showing the actual number of ticks
1273 !float_p)]; // and we want integer results
1274 # endif // !USE_IPHONE
1276 // #### Note: when the slider's range is large enough that we aren't
1277 // showing all possible ticks, the slider's value is not constrained
1278 // to be an integer, even though it should be...
1279 // Maybe we need to use a value converter or something?
1283 lab = [self makeLabel:label];
1284 [self placeChild:lab on:parent];
1287 CGFloat s = [NSFont systemFontSize] + 4;
1288 [lab setFont:[NSFont boldSystemFontOfSize:s]];
1295 lab = [self makeLabel:low_label];
1296 [lab setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
1298 [lab setAlignment:1]; // right aligned
1300 if (rect.size.width < LEFT_LABEL_WIDTH)
1301 rect.size.width = LEFT_LABEL_WIDTH; // make all left labels same size
1302 rect.size.height = [slider frame].size.height;
1303 [lab setFrame:rect];
1304 [self placeChild:lab on:parent];
1305 # else // USE_IPHONE
1306 [lab setTextAlignment: UITextAlignmentRight];
1307 [self placeChild:lab on:parent right:(label ? YES : NO)];
1308 # endif // USE_IPHONE
1314 [self placeChild:slider on:parent right:(low_label ? YES : NO)];
1315 # else // USE_IPHONE
1316 [self placeChild:slider on:parent right:(label || low_label ? YES : NO)];
1317 # endif // USE_IPHONE
1320 // Make left label be same height as slider.
1322 rect.size.height = [slider frame].size.height;
1323 [lab setFrame:rect];
1327 rect = [slider frame];
1328 if (rect.origin.x < LEFT_LABEL_WIDTH)
1329 rect.origin.x = LEFT_LABEL_WIDTH; // make unlabelled sliders line up too
1330 [slider setFrame:rect];
1334 lab = [self makeLabel:high_label];
1335 [lab setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
1338 // Make right label be same height as slider.
1339 rect.size.height = [slider frame].size.height;
1340 [lab setFrame:rect];
1341 [self placeChild:lab on:parent right:YES];
1345 [self bindSwitch:slider cmdline:arg];
1348 #ifndef USE_IPHONE // On iPhone, we use sliders for all numeric values.
1350 } else if ([type isEqualToString:@"spinbutton"]) {
1353 NSAssert1 (0, @"no _label in spinbutton %@", [node name]);
1356 NSAssert1 (!low_label,
1357 @"low-label not allowed in spinbutton \"%@\"", [node name]);
1358 NSAssert1 (!high_label,
1359 @"high-label not allowed in spinbutton \"%@\"", [node name]);
1360 NSAssert1 (!cvt, @"convert not allowed in spinbutton \"%@\"",
1364 rect.origin.x = rect.origin.y = 0;
1365 rect.size.width = rect.size.height = 10;
1367 NSTextField *txt = [[NSTextField alloc] initWithFrame:rect];
1368 [txt setStringValue:@"0000.0"];
1370 [txt setStringValue:@""];
1373 LABEL *lab = [self makeLabel:label];
1374 //[lab setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
1375 [lab setAlignment:1]; // right aligned
1377 if (rect.size.width < LEFT_LABEL_WIDTH)
1378 rect.size.width = LEFT_LABEL_WIDTH; // make all left labels same size
1379 rect.size.height = [txt frame].size.height;
1380 [lab setFrame:rect];
1381 [self placeChild:lab on:parent];
1385 [self placeChild:txt on:parent right:(label ? YES : NO)];
1389 if (rect.origin.x < LEFT_LABEL_WIDTH)
1390 rect.origin.x = LEFT_LABEL_WIDTH; // make unlabelled spinbtns line up
1391 [txt setFrame:rect];
1394 rect.size.width = rect.size.height = 10;
1395 NSStepper *step = [[NSStepper alloc] initWithFrame:rect];
1397 [self placeChild:step on:parent right:YES];
1398 rect = [step frame];
1399 rect.origin.x -= COLUMN_SPACING; // this one goes close
1400 rect.origin.y += ([txt frame].size.height - rect.size.height) / 2;
1401 [step setFrame:rect];
1403 [step setMinValue:[low doubleValue]];
1404 [step setMaxValue:[high doubleValue]];
1405 [step setAutorepeat:YES];
1406 [step setValueWraps:NO];
1408 double range = [high doubleValue] - [low doubleValue];
1410 [step setIncrement:range / 10.0];
1411 else if (range >= 500)
1412 [step setIncrement:range / 100.0];
1414 [step setIncrement:1.0];
1416 NSNumberFormatter *fmt = [[[NSNumberFormatter alloc] init] autorelease];
1417 [fmt setFormatterBehavior:NSNumberFormatterBehavior10_4];
1418 [fmt setNumberStyle:NSNumberFormatterDecimalStyle];
1419 [fmt setMinimum:[NSNumber numberWithDouble:[low doubleValue]]];
1420 [fmt setMaximum:[NSNumber numberWithDouble:[high doubleValue]]];
1421 [fmt setMinimumFractionDigits: (float_p ? 1 : 0)];
1422 [fmt setMaximumFractionDigits: (float_p ? 2 : 0)];
1424 [fmt setGeneratesDecimalNumbers:float_p];
1425 [[txt cell] setFormatter:fmt];
1427 [self bindSwitch:step cmdline:arg];
1428 [self bindSwitch:txt cmdline:arg];
1433 # endif // USE_IPHONE
1436 NSAssert2 (0, @"unknown type \"%@\" in \"%@\"", type, label);
1443 set_menu_item_object (NSMenuItem *item, NSObject *obj)
1445 /* If the object associated with this menu item looks like a boolean,
1446 store an NSNumber instead of an NSString, since that's what
1447 will be in the preferences (due to similar logic in PrefsReader).
1449 if ([obj isKindOfClass:[NSString class]]) {
1450 NSString *string = (NSString *) obj;
1451 if (NSOrderedSame == [string caseInsensitiveCompare:@"true"] ||
1452 NSOrderedSame == [string caseInsensitiveCompare:@"yes"])
1453 obj = [NSNumber numberWithBool:YES];
1454 else if (NSOrderedSame == [string caseInsensitiveCompare:@"false"] ||
1455 NSOrderedSame == [string caseInsensitiveCompare:@"no"])
1456 obj = [NSNumber numberWithBool:NO];
1461 [item setRepresentedObject:obj];
1462 //NSLog (@"menu item \"%@\" = \"%@\" %@", [item title], obj, [obj class]);
1464 # endif // !USE_IPHONE
1467 /* Creates the popup menu described by the given XML node (and its children).
1469 - (void) makeOptionMenu:(NSXMLNode *)node on:(NSView *)parent
1471 NSArray *children = [node children];
1472 int i, count = [children count];
1475 NSAssert1 (0, @"no menu items in \"%@\"", [node name]);
1479 // get the "id" attribute off the <select> tag.
1481 NSMutableDictionary *dict =
1482 [NSMutableDictionary dictionaryWithObjectsAndKeys:
1485 [self parseAttrs:dict node:node];
1488 rect.origin.x = rect.origin.y = 0;
1489 rect.size.width = 10;
1490 rect.size.height = 10;
1492 NSString *menu_key = nil; // the resource key used by items in this menu
1495 // #### "Build and Analyze" says that all of our widgets leak, because it
1496 // seems to not realize that placeChild -> addSubview retains them.
1497 // Not sure what to do to make these warnings go away.
1499 NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:rect
1501 NSMenuItem *def_item = nil;
1502 float max_width = 0;
1504 # else // USE_IPHONE
1506 NSString *def_item = nil;
1508 rect.size.width = 0;
1509 rect.size.height = 0;
1510 # ifdef USE_PICKER_VIEW
1511 UIPickerView *popup = [[[UIPickerView alloc] initWithFrame:rect] retain];
1512 popup.delegate = self;
1513 popup.dataSource = self;
1514 # endif // !USE_PICKER_VIEW
1515 NSMutableArray *items = [NSMutableArray arrayWithCapacity:10];
1517 # endif // USE_IPHONE
1519 for (i = 0; i < count; i++) {
1520 NSXMLNode *child = [children objectAtIndex:i];
1522 if ([child kind] == NSXMLCommentKind)
1524 if ([child kind] != NSXMLElementKind) {
1525 // NSAssert2 (0, @"weird XML node kind: %d: %@", (int)[child kind], node);
1529 // get the "id", "_label", and "arg-set" attrs off of the <option> tags.
1531 NSMutableDictionary *dict2 =
1532 [NSMutableDictionary dictionaryWithObjectsAndKeys:
1537 [self parseAttrs:dict2 node:child];
1538 NSString *label = [dict2 objectForKey:@"_label"];
1539 NSString *arg_set = [dict2 objectForKey:@"arg-set"];
1542 NSAssert1 (0, @"no _label in %@", [child name]);
1547 // create the menu item (and then get a pointer to it)
1548 [popup addItemWithTitle:label];
1549 NSMenuItem *item = [popup itemWithTitle:label];
1550 # endif // USE_IPHONE
1553 NSString *this_val = NULL;
1554 NSString *this_key = [self switchToResource: arg_set
1557 NSAssert1 (this_val, @"this_val null for %@", arg_set);
1558 if (menu_key && ![menu_key isEqualToString:this_key])
1560 @"multiple resources in menu: \"%@\" vs \"%@\" = \"%@\"",
1561 menu_key, this_key, this_val);
1563 menu_key = this_key;
1565 /* If this menu has the cmd line "-mode foo" then set this item's
1566 value to "foo" (the menu itself will be bound to e.g. "modeString")
1569 set_menu_item_object (item, this_val);
1571 // Array holds ["Label", "resource-key", "resource-val"].
1572 [items addObject:[NSMutableArray arrayWithObjects:
1573 label, @"", this_val, nil]];
1577 // no arg-set -- only one menu item can be missing that.
1578 NSAssert1 (!def_item, @"no arg-set in \"%@\"", label);
1583 // Array holds ["Label", "resource-key", "resource-val"].
1584 [items addObject:[NSMutableArray arrayWithObjects:
1585 label, @"", @"", nil]];
1589 /* make sure the menu button has room for the text of this item,
1590 and remember the greatest width it has reached.
1593 [popup setTitle:label];
1595 NSRect r = [popup frame];
1596 if (r.size.width > max_width) max_width = r.size.width;
1597 # endif // USE_IPHONE
1601 NSAssert1 (0, @"no switches in menu \"%@\"", [dict objectForKey:@"id"]);
1605 /* We've added all of the menu items. If there was an item with no
1606 command-line switch, then it's the item that represents the default
1607 value. Now we must bind to that item as well... (We have to bind
1608 this one late, because if it was the first item, then we didn't
1609 yet know what resource was associated with this menu.)
1612 NSObject *def_obj = [defaultOptions objectForKey:menu_key];
1614 @"no default value for resource \"%@\" in menu item \"%@\"",
1624 set_menu_item_object (def_item, def_obj);
1625 # else // !USE_IPHONE
1626 for (NSMutableArray *a in items) {
1627 // Make sure each array contains the resource key.
1628 [a replaceObjectAtIndex:1 withObject:menu_key];
1629 // Make sure the default item contains the default resource value.
1630 if (def_obj && def_item &&
1631 [def_item isEqualToString:[a objectAtIndex:0]])
1632 [a replaceObjectAtIndex:2 withObject:def_obj];
1634 # endif // !USE_IPHONE
1638 # ifdef USE_PICKER_VIEW
1639 /* Finish tweaking the menu button itself.
1642 [popup setTitle:[def_item title]];
1643 NSRect r = [popup frame];
1644 r.size.width = max_width;
1646 # endif // USE_PICKER_VIEW
1649 # if !defined(USE_IPHONE) || defined(USE_PICKER_VIEW)
1650 [self placeChild:popup on:parent];
1651 [self bindResource:popup key:menu_key];
1656 # ifdef USE_PICKER_VIEW
1657 // Store the items for this picker in the picker_values array.
1658 // This is so fucking stupid.
1660 int menu_number = [pref_keys count] - 1;
1661 if (! picker_values)
1662 picker_values = [[NSMutableArray arrayWithCapacity:menu_number] retain];
1663 while ([picker_values count] <= menu_number)
1664 [picker_values addObject:[NSArray arrayWithObjects: nil]];
1665 [picker_values replaceObjectAtIndex:menu_number withObject:items];
1666 [popup reloadAllComponents];
1668 # else // !USE_PICKER_VIEW
1670 [self placeSeparator];
1673 for (NSArray *item in items) {
1674 RadioButton *b = [[RadioButton alloc] initWithIndex:i
1676 [b setLineBreakMode:UILineBreakModeHeadTruncation];
1677 [b setFont:[NSFont boldSystemFontOfSize: FONT_SIZE]];
1678 [self placeChild:b on:parent];
1682 [self placeSeparator];
1684 # endif // !USE_PICKER_VIEW
1685 # endif // !USE_IPHONE
1690 /* Creates an uneditable, wrapping NSTextField to display the given
1691 text enclosed by <description> ... </description> in the XML.
1693 - (void) makeDescLabel:(NSXMLNode *)node on:(NSView *)parent
1695 NSString *text = nil;
1696 NSArray *children = [node children];
1697 int i, count = [children count];
1699 for (i = 0; i < count; i++) {
1700 NSXMLNode *child = [children objectAtIndex:i];
1701 NSString *s = [child objectValue];
1703 text = [text stringByAppendingString:s];
1708 text = unwrap (text);
1710 NSRect rect = [parent frame];
1711 rect.origin.x = rect.origin.y = 0;
1712 rect.size.width = 200;
1713 rect.size.height = 50; // sized later
1715 NSText *lab = [[NSText alloc] initWithFrame:rect];
1716 [lab setEditable:NO];
1717 [lab setDrawsBackground:NO];
1718 [lab setHorizontallyResizable:YES];
1719 [lab setVerticallyResizable:YES];
1720 [lab setString:text];
1725 # else // USE_IPHONE
1727 # ifndef USE_HTML_LABELS
1729 UILabel *lab = [self makeLabel:text];
1730 [lab setFont:[NSFont systemFontOfSize: [NSFont systemFontSize]]];
1733 # else // USE_HTML_LABELS
1734 HTMLLabel *lab = [[HTMLLabel alloc]
1736 font:[NSFont systemFontOfSize: [NSFont systemFontSize]]];
1737 [lab setFrame:rect];
1739 # endif // USE_HTML_LABELS
1741 [self placeSeparator];
1743 # endif // USE_IPHONE
1745 [self placeChild:lab on:parent];
1750 /* Creates the NSTextField described by the given XML node.
1752 - (void) makeTextField: (NSXMLNode *)node
1753 on: (NSView *)parent
1754 withLabel: (BOOL) label_p
1755 horizontal: (BOOL) horiz_p
1757 NSMutableDictionary *dict =
1758 [NSMutableDictionary dictionaryWithObjectsAndKeys:
1763 [self parseAttrs:dict node:node];
1764 NSString *label = [dict objectForKey:@"_label"];
1765 NSString *arg = [dict objectForKey:@"arg"];
1767 if (!label && label_p) {
1768 NSAssert1 (0, @"no _label in %@", [node name]);
1772 NSAssert1 (arg, @"no arg in %@", label);
1775 rect.origin.x = rect.origin.y = 0;
1776 rect.size.width = rect.size.height = 10;
1778 NSTextField *txt = [[NSTextField alloc] initWithFrame:rect];
1782 // make the default size be around 30 columns; a typical value for
1783 // these text fields is "xscreensaver-text --cols 40".
1785 [txt setStringValue:@"123456789 123456789 123456789 "];
1787 [[txt cell] setWraps:NO];
1788 [[txt cell] setScrollable:YES];
1789 [txt setStringValue:@""];
1791 # else // USE_IPHONE
1793 txt.adjustsFontSizeToFitWidth = YES;
1794 txt.textColor = [UIColor blackColor];
1795 txt.font = [UIFont systemFontOfSize: FONT_SIZE];
1796 txt.placeholder = @"";
1797 txt.borderStyle = UITextBorderStyleRoundedRect;
1798 txt.textAlignment = UITextAlignmentRight;
1799 txt.keyboardType = UIKeyboardTypeDefault; // Full kbd
1800 txt.autocorrectionType = UITextAutocorrectionTypeNo;
1801 txt.autocapitalizationType = UITextAutocapitalizationTypeNone;
1802 txt.clearButtonMode = UITextFieldViewModeAlways;
1803 txt.returnKeyType = UIReturnKeyDone;
1804 txt.delegate = self;
1806 [txt setEnabled: YES];
1808 rect.size.height = [txt.font lineHeight] * 1.2;
1809 [txt setFrame:rect];
1811 # endif // USE_IPHONE
1814 LABEL *lab = [self makeLabel:label];
1815 [self placeChild:lab on:parent];
1819 [self placeChild:txt on:parent right:(label ? YES : NO)];
1821 [self bindSwitch:txt cmdline:arg];
1826 /* Creates the NSTextField described by the given XML node,
1827 and hooks it up to a Choose button and a file selector widget.
1829 - (void) makeFileSelector: (NSXMLNode *)node
1830 on: (NSView *)parent
1831 dirsOnly: (BOOL) dirsOnly
1832 withLabel: (BOOL) label_p
1833 editable: (BOOL) editable_p
1835 # ifndef USE_IPHONE // No files. No selectors.
1836 NSMutableDictionary *dict =
1837 [NSMutableDictionary dictionaryWithObjectsAndKeys:
1842 [self parseAttrs:dict node:node];
1843 NSString *label = [dict objectForKey:@"_label"];
1844 NSString *arg = [dict objectForKey:@"arg"];
1846 if (!label && label_p) {
1847 NSAssert1 (0, @"no _label in %@", [node name]);
1851 NSAssert1 (arg, @"no arg in %@", label);
1854 rect.origin.x = rect.origin.y = 0;
1855 rect.size.width = rect.size.height = 10;
1857 NSTextField *txt = [[NSTextField alloc] initWithFrame:rect];
1859 // make the default size be around 20 columns.
1861 [txt setStringValue:@"123456789 123456789 "];
1863 [txt setSelectable:YES];
1864 [txt setEditable:editable_p];
1865 [txt setBezeled:editable_p];
1866 [txt setDrawsBackground:editable_p];
1867 [[txt cell] setWraps:NO];
1868 [[txt cell] setScrollable:YES];
1869 [[txt cell] setLineBreakMode:NSLineBreakByTruncatingHead];
1870 [txt setStringValue:@""];
1874 lab = [self makeLabel:label];
1875 [self placeChild:lab on:parent];
1879 [self placeChild:txt on:parent right:(label ? YES : NO)];
1881 [self bindSwitch:txt cmdline:arg];
1884 // Make the text field and label be the same height, whichever is taller.
1887 rect.size.height = ([lab frame].size.height > [txt frame].size.height
1888 ? [lab frame].size.height
1889 : [txt frame].size.height);
1890 [txt setFrame:rect];
1893 // Now put a "Choose" button next to it.
1895 rect.origin.x = rect.origin.y = 0;
1896 rect.size.width = rect.size.height = 10;
1897 NSButton *choose = [[NSButton alloc] initWithFrame:rect];
1898 [choose setTitle:@"Choose..."];
1899 [choose setBezelStyle:NSRoundedBezelStyle];
1902 [self placeChild:choose on:parent right:YES];
1904 // center the Choose button around the midpoint of the text field.
1905 rect = [choose frame];
1906 rect.origin.y = ([txt frame].origin.y +
1907 (([txt frame].size.height - rect.size.height) / 2));
1908 [choose setFrameOrigin:rect.origin];
1910 [choose setTarget:[parent window]];
1912 [choose setAction:@selector(fileSelectorChooseDirsAction:)];
1914 [choose setAction:@selector(fileSelectorChooseAction:)];
1917 # endif // !USE_IPHONE
1923 /* Runs a modal file selector and sets the text field's value to the
1924 selected file or directory.
1927 do_file_selector (NSTextField *txt, BOOL dirs_p)
1929 NSOpenPanel *panel = [NSOpenPanel openPanel];
1930 [panel setAllowsMultipleSelection:NO];
1931 [panel setCanChooseFiles:!dirs_p];
1932 [panel setCanChooseDirectories:dirs_p];
1934 NSString *file = [txt stringValue];
1935 if ([file length] <= 0) {
1936 file = NSHomeDirectory();
1938 file = [file stringByAppendingPathComponent:@"Pictures"];
1941 // NSString *dir = [file stringByDeletingLastPathComponent];
1943 int result = [panel runModalForDirectory:file //dir
1944 file:nil //[file lastPathComponent]
1946 if (result == NSOKButton) {
1947 NSArray *files = [panel filenames];
1948 file = ([files count] > 0 ? [files objectAtIndex:0] : @"");
1949 file = [file stringByAbbreviatingWithTildeInPath];
1950 [txt setStringValue:file];
1952 // Fuck me! Just setting the value of the NSTextField does not cause
1953 // that to end up in the preferences!
1955 NSDictionary *dict = [txt infoForBinding:@"value"];
1956 NSUserDefaultsController *prefs = [dict objectForKey:@"NSObservedObject"];
1957 NSString *path = [dict objectForKey:@"NSObservedKeyPath"];
1958 if ([path hasPrefix:@"values."]) // WTF.
1959 path = [path substringFromIndex:7];
1960 [[prefs values] setValue:file forKey:path];
1963 // make sure the end of the string is visible.
1964 NSText *fe = [[txt window] fieldEditor:YES forObject:txt];
1966 range.location = [file length]-3;
1968 if (! [[txt window] makeFirstResponder:[txt window]])
1969 [[txt window] endEditingFor:nil];
1970 // [[txt window] makeFirstResponder:nil];
1971 [fe setSelectedRange:range];
1972 // [tv scrollRangeToVisible:range];
1973 // [txt setNeedsDisplay:YES];
1974 // [[txt cell] setNeedsDisplay:YES];
1975 // [txt selectAll:txt];
1981 /* Returns the NSTextField that is to the left of or above the NSButton.
1983 static NSTextField *
1984 find_text_field_of_button (NSButton *button)
1986 NSView *parent = [button superview];
1987 NSArray *kids = [parent subviews];
1988 int nkids = [kids count];
1991 for (i = 0; i < nkids; i++) {
1992 NSObject *kid = [kids objectAtIndex:i];
1993 if ([kid isKindOfClass:[NSTextField class]]) {
1994 f = (NSTextField *) kid;
1995 } else if (kid == button) {
2004 - (void) fileSelectorChooseAction:(NSObject *)arg
2006 NSButton *choose = (NSButton *) arg;
2007 NSTextField *txt = find_text_field_of_button (choose);
2008 do_file_selector (txt, NO);
2011 - (void) fileSelectorChooseDirsAction:(NSObject *)arg
2013 NSButton *choose = (NSButton *) arg;
2014 NSTextField *txt = find_text_field_of_button (choose);
2015 do_file_selector (txt, YES);
2018 #endif // !USE_IPHONE
2021 - (void) makeTextLoaderControlBox:(NSXMLNode *)node on:(NSView *)parent
2026 (x) Computer name and time
2027 ( ) Text [__________________________]
2028 ( ) Text file [_________________] [Choose]
2029 ( ) URL [__________________________]
2030 ( ) Shell Cmd [__________________________]
2032 textMode -text-mode date
2033 textMode -text-mode literal textLiteral -text-literal %
2034 textMode -text-mode file textFile -text-file %
2035 textMode -text-mode url textURL -text-url %
2036 textMode -text-mode program textProgram -text-program %
2039 rect.size.width = rect.size.height = 1;
2040 rect.origin.x = rect.origin.y = 0;
2041 NSView *group = [[NSView alloc] initWithFrame:rect];
2042 NSView *rgroup = [[NSView alloc] initWithFrame:rect];
2044 Bool program_p = TRUE;
2049 // This is how you link radio buttons together.
2051 NSButtonCell *proto = [[NSButtonCell alloc] init];
2052 [proto setButtonType:NSRadioButton];
2054 rect.origin.x = rect.origin.y = 0;
2055 rect.size.width = rect.size.height = 10;
2056 NSMatrix *matrix = [[NSMatrix alloc]
2058 mode:NSRadioModeMatrix
2060 numberOfRows: 4 + (program_p ? 1 : 0)
2062 [matrix setAllowsEmptySelection:NO];
2064 NSArrayController *cnames = [[NSArrayController alloc] initWithContent:nil];
2065 [cnames addObject:@"Computer name and time"];
2066 [cnames addObject:@"Text"];
2067 [cnames addObject:@"File"];
2068 [cnames addObject:@"URL"];
2069 if (program_p) [cnames addObject:@"Shell Cmd"];
2070 [matrix bind:@"content"
2072 withKeyPath:@"arrangedObjects"
2076 [self bindSwitch:matrix cmdline:@"-text-mode %"];
2078 [self placeChild:matrix on:group];
2079 [self placeChild:rgroup on:group right:YES];
2083 # else // USE_IPHONE
2085 NSView *rgroup = parent;
2088 // <select id="textMode">
2089 // <option id="date" _label="Display date" arg-set="-text-mode date"/>
2090 // <option id="text" _label="Display text" arg-set="-text-mode literal"/>
2091 // <option id="url" _label="Display URL"/>
2094 node2 = [[NSXMLElement alloc] initWithName:@"select"];
2095 [node2 setAttributesAsDictionary:
2096 [NSDictionary dictionaryWithObjectsAndKeys:
2100 NSXMLNode *node3 = [[NSXMLElement alloc] initWithName:@"option"];
2101 [node3 setAttributesAsDictionary:
2102 [NSDictionary dictionaryWithObjectsAndKeys:
2104 @"-text-mode date", @"arg-set",
2105 @"Display the date and time", @"_label",
2107 [node3 setParent: node2];
2110 node3 = [[NSXMLElement alloc] initWithName:@"option"];
2111 [node3 setAttributesAsDictionary:
2112 [NSDictionary dictionaryWithObjectsAndKeys:
2114 @"-text-mode literal", @"arg-set",
2115 @"Display static text", @"_label",
2117 [node3 setParent: node2];
2120 node3 = [[NSXMLElement alloc] initWithName:@"option"];
2121 [node3 setAttributesAsDictionary:
2122 [NSDictionary dictionaryWithObjectsAndKeys:
2124 @"Display the contents of a URL", @"_label",
2126 [node3 setParent: node2];
2129 [self makeOptionMenu:node2 on:rgroup];
2131 # endif // USE_IPHONE
2134 // <string id="textLiteral" _label="" arg-set="-text-literal %"/>
2135 node2 = [[NSXMLElement alloc] initWithName:@"string"];
2136 [node2 setAttributesAsDictionary:
2137 [NSDictionary dictionaryWithObjectsAndKeys:
2138 @"textLiteral", @"id",
2139 @"-text-literal %", @"arg",
2141 @"Text to display", @"_label",
2144 [self makeTextField:node2 on:rgroup
2152 // rect = [last_child(rgroup) frame];
2154 /* // trying to make the text fields be enabled only when the checkbox is on..
2155 control = last_child (rgroup);
2156 [control bind:@"enabled"
2157 toObject:[matrix cellAtRow:1 column:0]
2158 withKeyPath:@"value"
2164 // <file id="textFile" _label="" arg-set="-text-file %"/>
2165 node2 = [[NSXMLElement alloc] initWithName:@"string"];
2166 [node2 setAttributesAsDictionary:
2167 [NSDictionary dictionaryWithObjectsAndKeys:
2169 @"-text-file %", @"arg",
2171 [self makeFileSelector:node2 on:rgroup
2172 dirsOnly:NO withLabel:NO editable:NO];
2173 # endif // !USE_IPHONE
2175 // rect = [last_child(rgroup) frame];
2177 // <string id="textURL" _label="" arg-set="text-url %"/>
2178 node2 = [[NSXMLElement alloc] initWithName:@"string"];
2179 [node2 setAttributesAsDictionary:
2180 [NSDictionary dictionaryWithObjectsAndKeys:
2182 @"-text-url %", @"arg",
2184 @"URL to display", @"_label",
2187 [self makeTextField:node2 on:rgroup
2195 // rect = [last_child(rgroup) frame];
2199 // <string id="textProgram" _label="" arg-set="text-program %"/>
2200 node2 = [[NSXMLElement alloc] initWithName:@"string"];
2201 [node2 setAttributesAsDictionary:
2202 [NSDictionary dictionaryWithObjectsAndKeys:
2203 @"textProgram", @"id",
2204 @"-text-program %", @"arg",
2206 [self makeTextField:node2 on:rgroup withLabel:NO horizontal:NO];
2209 // rect = [last_child(rgroup) frame];
2211 layout_group (rgroup, NO);
2213 rect = [rgroup frame];
2214 rect.size.width += 35; // WTF? Why is rgroup too narrow?
2215 [rgroup setFrame:rect];
2218 // Set the height of the cells in the radio-box matrix to the height of
2219 // the (last of the) text fields.
2220 control = last_child (rgroup);
2221 rect = [control frame];
2222 rect.size.width = 30; // width of the string "Text", plus a bit...
2224 rect.size.width += 25;
2225 rect.size.height += LINE_SPACING;
2226 [matrix setCellSize:rect.size];
2227 [matrix sizeToCells];
2229 layout_group (group, YES);
2230 rect = [matrix frame];
2231 rect.origin.x += rect.size.width + COLUMN_SPACING;
2232 rect.origin.y -= [control frame].size.height - LINE_SPACING;
2233 [rgroup setFrameOrigin:rect.origin];
2235 // now cheat on the size of the matrix: allow it to overlap (underlap)
2238 rect.size = [matrix cellSize];
2239 rect.size.width = 300;
2240 [matrix setCellSize:rect.size];
2241 [matrix sizeToCells];
2243 // Cheat on the position of the stuff on the right (the rgroup).
2244 // GAAAH, this code is such crap!
2245 rect = [rgroup frame];
2247 [rgroup setFrame:rect];
2250 rect.size.width = rect.size.height = 0;
2251 NSBox *box = [[NSBox alloc] initWithFrame:rect];
2252 [box setTitlePosition:NSAtTop];
2253 [box setBorderType:NSBezelBorder];
2254 [box setTitle:@"Display Text"];
2256 rect.size.width = rect.size.height = 12;
2257 [box setContentViewMargins:rect.size];
2258 [box setContentView:group];
2261 [self placeChild:box on:parent];
2263 # endif // !USE_IPHONE
2267 - (void) makeImageLoaderControlBox:(NSXMLNode *)node on:(NSView *)parent
2270 [x] Grab desktop images
2271 [ ] Choose random image:
2272 [__________________________] [Choose]
2274 <boolean id="grabDesktopImages" _label="Grab desktop images"
2275 arg-unset="-no-grab-desktop"/>
2276 <boolean id="chooseRandomImages" _label="Grab desktop images"
2277 arg-unset="-choose-random-images"/>
2278 <file id="imageDirectory" _label="" arg-set="-image-directory %"/>
2281 NSXMLElement *node2;
2284 # define SCREENS "Grab desktop images"
2285 # define PHOTOS "Choose random images"
2287 # define SCREENS "Grab screenshots"
2288 # define PHOTOS "Use photo library"
2291 node2 = [[NSXMLElement alloc] initWithName:@"boolean"];
2292 [node2 setAttributesAsDictionary:
2293 [NSDictionary dictionaryWithObjectsAndKeys:
2294 @"grabDesktopImages", @"id",
2295 @ SCREENS, @"_label",
2296 @"-no-grab-desktop", @"arg-unset",
2298 [self makeCheckbox:node2 on:parent];
2300 node2 = [[NSXMLElement alloc] initWithName:@"boolean"];
2301 [node2 setAttributesAsDictionary:
2302 [NSDictionary dictionaryWithObjectsAndKeys:
2303 @"chooseRandomImages", @"id",
2304 @ PHOTOS, @"_label",
2305 @"-choose-random-images", @"arg-set",
2307 [self makeCheckbox:node2 on:parent];
2309 node2 = [[NSXMLElement alloc] initWithName:@"string"];
2310 [node2 setAttributesAsDictionary:
2311 [NSDictionary dictionaryWithObjectsAndKeys:
2312 @"imageDirectory", @"id",
2313 @"Images from:", @"_label",
2314 @"-image-directory %", @"arg",
2316 [self makeFileSelector:node2 on:parent
2317 dirsOnly:YES withLabel:YES editable:YES];
2323 // Add a second, explanatory label below the file/URL selector.
2326 lab2 = [self makeLabel:@"(Local folder, or URL of RSS or Atom feed)"];
2327 [self placeChild:lab2 on:parent];
2329 // Pack it in a little tighter vertically.
2330 NSRect r2 = [lab2 frame];
2333 [lab2 setFrameOrigin:r2.origin];
2335 # endif // USE_IPHONE
2339 #pragma mark Layout for controls
2344 last_child (NSView *parent)
2346 NSArray *kids = [parent subviews];
2347 int nkids = [kids count];
2351 return [kids objectAtIndex:nkids-1];
2353 #endif // USE_IPHONE
2356 /* Add the child as a subview of the parent, positioning it immediately
2357 below or to the right of the previously-added child of that view.
2359 - (void) placeChild:
2365 on:(NSView *)parent right:(BOOL)right_p
2368 NSRect rect = [child frame];
2369 NSView *last = last_child (parent);
2371 rect.origin.x = LEFT_MARGIN;
2372 rect.origin.y = ([parent frame].size.height - rect.size.height
2374 } else if (right_p) {
2375 rect = [last frame];
2376 rect.origin.x += rect.size.width + COLUMN_SPACING;
2378 rect = [last frame];
2379 rect.origin.x = LEFT_MARGIN;
2380 rect.origin.y -= [child frame].size.height + LINE_SPACING;
2382 NSRect r = [child frame];
2383 r.origin = rect.origin;
2385 [parent addSubview:child];
2387 # else // USE_IPHONE
2389 // Controls is an array of arrays of the controls, divided into sections.
2391 controls = [[NSMutableArray arrayWithCapacity:10] retain];
2392 if ([controls count] == 0)
2393 [controls addObject: [NSMutableArray arrayWithCapacity:10]];
2394 NSMutableArray *current = [controls objectAtIndex:[controls count]-1];
2396 if (!right_p || [current count] == 0) {
2397 // Nothing on the current line. Add this object.
2398 [current addObject: child];
2400 // Something's on the current line already.
2401 NSObject *old = [current objectAtIndex:[current count]-1];
2402 if ([old isKindOfClass:[NSMutableArray class]]) {
2403 // Already an array in this cell. Append.
2404 NSAssert ([(NSArray *) old count] < 4, @"internal error");
2405 [(NSMutableArray *) old addObject: child];
2407 // Replace the control in this cell with an array, then app
2408 NSMutableArray *a = [NSMutableArray arrayWithObjects: old, child, nil];
2409 [current replaceObjectAtIndex:[current count]-1 withObject:a];
2412 # endif // USE_IPHONE
2416 - (void) placeChild:(NSView *)child on:(NSView *)parent
2418 [self placeChild:child on:parent right:NO];
2424 // Start putting subsequent children in a new group, to create a new
2425 // section on the UITableView.
2427 - (void) placeSeparator
2429 if (! controls) return;
2430 if ([controls count] == 0) return;
2431 if ([[controls objectAtIndex:[controls count]-1]
2433 [controls addObject: [NSMutableArray arrayWithCapacity:10]];
2435 #endif // USE_IPHONE
2439 /* Creates an invisible NSBox (for layout purposes) to enclose the widgets
2440 wrapped in <hgroup> or <vgroup> in the XML.
2442 - (void) makeGroup:(NSXMLNode *)node
2444 horizontal:(BOOL) horiz_p
2447 if (!horiz_p) [self placeSeparator];
2448 [self traverseChildren:node on:parent];
2449 if (!horiz_p) [self placeSeparator];
2450 # else // !USE_IPHONE
2452 rect.size.width = rect.size.height = 1;
2453 rect.origin.x = rect.origin.y = 0;
2454 NSView *group = [[NSView alloc] initWithFrame:rect];
2455 [self traverseChildren:node on:group];
2457 layout_group (group, horiz_p);
2459 rect.size.width = rect.size.height = 0;
2460 NSBox *box = [[NSBox alloc] initWithFrame:rect];
2461 [box setTitlePosition:NSNoTitle];
2462 [box setBorderType:NSNoBorder];
2463 [box setContentViewMargins:rect.size];
2464 [box setContentView:group];
2467 [self placeChild:box on:parent];
2468 # endif // !USE_IPHONE
2474 layout_group (NSView *group, BOOL horiz_p)
2476 NSArray *kids = [group subviews];
2477 int nkids = [kids count];
2479 double maxx = 0, miny = 0;
2480 for (i = 0; i < nkids; i++) {
2481 NSView *kid = [kids objectAtIndex:i];
2482 NSRect r = [kid frame];
2485 maxx += r.size.width + COLUMN_SPACING;
2486 if (r.size.height > -miny) miny = -r.size.height;
2488 if (r.size.width > maxx) maxx = r.size.width;
2489 miny = r.origin.y - r.size.height;
2496 rect.size.width = maxx;
2497 rect.size.height = -miny;
2498 [group setFrame:rect];
2501 for (i = 0; i < nkids; i++) {
2502 NSView *kid = [kids objectAtIndex:i];
2503 NSRect r = [kid frame];
2505 r.origin.y = rect.size.height - r.size.height;
2507 x += r.size.width + COLUMN_SPACING;
2514 #endif // !USE_IPHONE
2517 /* Create some kind of control corresponding to the given XML node.
2519 -(void)makeControl:(NSXMLNode *)node on:(NSView *)parent
2521 NSString *name = [node name];
2523 if ([node kind] == NSXMLCommentKind)
2526 if ([node kind] == NSXMLTextKind) {
2527 NSString *s = [(NSString *) [node objectValue]
2528 stringByTrimmingCharactersInSet:
2529 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
2530 if (! [s isEqualToString:@""]) {
2531 NSAssert1 (0, @"unexpected text: %@", s);
2536 if ([node kind] != NSXMLElementKind) {
2537 NSAssert2 (0, @"weird XML node kind: %d: %@", (int)[node kind], node);
2541 if ([name isEqualToString:@"hgroup"] ||
2542 [name isEqualToString:@"vgroup"]) {
2544 [self makeGroup:node on:parent
2545 horizontal:[name isEqualToString:@"hgroup"]];
2547 } else if ([name isEqualToString:@"command"]) {
2548 // do nothing: this is the "-root" business
2550 } else if ([name isEqualToString:@"boolean"]) {
2551 [self makeCheckbox:node on:parent];
2553 } else if ([name isEqualToString:@"string"]) {
2554 [self makeTextField:node on:parent withLabel:NO horizontal:NO];
2556 } else if ([name isEqualToString:@"file"]) {
2557 [self makeFileSelector:node on:parent
2558 dirsOnly:NO withLabel:YES editable:NO];
2560 } else if ([name isEqualToString:@"number"]) {
2561 [self makeNumberSelector:node on:parent];
2563 } else if ([name isEqualToString:@"select"]) {
2564 [self makeOptionMenu:node on:parent];
2566 } else if ([name isEqualToString:@"_description"]) {
2567 [self makeDescLabel:node on:parent];
2569 } else if ([name isEqualToString:@"xscreensaver-text"]) {
2570 [self makeTextLoaderControlBox:node on:parent];
2572 } else if ([name isEqualToString:@"xscreensaver-image"]) {
2573 [self makeImageLoaderControlBox:node on:parent];
2576 NSAssert1 (0, @"unknown tag: %@", name);
2581 /* Iterate over and process the children of this XML node.
2583 - (void)traverseChildren:(NSXMLNode *)node on:(NSView *)parent
2585 NSArray *children = [node children];
2586 int i, count = [children count];
2587 for (i = 0; i < count; i++) {
2588 NSXMLNode *child = [children objectAtIndex:i];
2589 [self makeControl:child on:parent];
2596 /* Kludgey magic to make the window enclose the controls we created.
2599 fix_contentview_size (NSView *parent)
2602 NSArray *kids = [parent subviews];
2603 int nkids = [kids count];
2604 NSView *text = 0; // the NSText at the bottom of the window
2605 double maxx = 0, miny = 0;
2608 /* Find the size of the rectangle taken up by each of the children
2609 except the final "NSText" child.
2611 for (i = 0; i < nkids; i++) {
2612 NSView *kid = [kids objectAtIndex:i];
2613 if ([kid isKindOfClass:[NSText class]]) {
2618 if (f.origin.x + f.size.width > maxx) maxx = f.origin.x + f.size.width;
2619 if (f.origin.y - f.size.height < miny) miny = f.origin.y;
2620 // NSLog(@"start: %3.0f x %3.0f @ %3.0f %3.0f %3.0f %@",
2621 // f.size.width, f.size.height, f.origin.x, f.origin.y,
2622 // f.origin.y + f.size.height, [kid class]);
2625 if (maxx < 400) maxx = 400; // leave room for the NSText paragraph...
2627 /* Now that we know the width of the window, set the width of the NSText to
2628 that, so that it can decide what its height needs to be.
2630 if (! text) abort();
2632 // NSLog(@"text old: %3.0f x %3.0f @ %3.0f %3.0f %3.0f %@",
2633 // f.size.width, f.size.height, f.origin.x, f.origin.y,
2634 // f.origin.y + f.size.height, [text class]);
2636 // set the NSText's width (this changes its height).
2637 f.size.width = maxx - LEFT_MARGIN;
2640 // position the NSText below the last child (this gives us a new miny).
2642 f.origin.y = miny - f.size.height - LINE_SPACING;
2643 miny = f.origin.y - LINE_SPACING;
2646 // Lock the width of the field and unlock the height, and let it resize
2647 // once more, to compute the proper height of the text for that width.
2649 [(NSText *) text setHorizontallyResizable:NO];
2650 [(NSText *) text setVerticallyResizable:YES];
2651 [(NSText *) text sizeToFit];
2653 // Now lock the height too: no more resizing this text field.
2655 [(NSText *) text setVerticallyResizable:NO];
2657 // Now reposition the top edge of the text field to be back where it
2658 // was before we changed the height.
2660 float oh = f.size.height;
2662 float dh = f.size.height - oh;
2665 // #### This is needed in OSX 10.5, but is wrong in OSX 10.6. WTF??
2666 // If we do this in 10.6, the text field moves down, off the window.
2667 // So instead we repair it at the end, at the "WTF2" comment.
2670 // Also adjust the parent height by the change in height of the text field.
2673 // NSLog(@"text new: %3.0f x %3.0f @ %3.0f %3.0f %3.0f %@",
2674 // f.size.width, f.size.height, f.origin.x, f.origin.y,
2675 // f.origin.y + f.size.height, [text class]);
2678 /* Set the contentView to the size of the children.
2681 // float yoff = f.size.height;
2682 f.size.width = maxx + LEFT_MARGIN;
2683 f.size.height = -(miny - LEFT_MARGIN*2);
2684 // yoff = f.size.height - yoff;
2685 [parent setFrame:f];
2687 // NSLog(@"max: %3.0f x %3.0f @ %3.0f %3.0f",
2688 // f.size.width, f.size.height, f.origin.x, f.origin.y);
2690 /* Now move all of the kids up into the window.
2693 float shift = f.size.height;
2694 // NSLog(@"shift: %3.0f", shift);
2695 for (i = 0; i < nkids; i++) {
2696 NSView *kid = [kids objectAtIndex:i];
2698 f.origin.y += shift;
2700 // NSLog(@"move: %3.0f x %3.0f @ %3.0f %3.0f %3.0f %@",
2701 // f.size.width, f.size.height, f.origin.x, f.origin.y,
2702 // f.origin.y + f.size.height, [kid class]);
2707 parent: 420 x 541 @ 0 0
2708 text: 380 x 100 @ 20 22 miny=-501
2711 parent: 420 x 541 @ 0 0
2712 text: 380 x 100 @ 20 50 miny=-501
2715 // #### WTF2: See "WTF" above. If the text field is off the screen,
2716 // move it up. We need this on 10.6 but not on 10.5. Auugh.
2719 if (f.origin.y < 50) { // magic numbers, yay
2724 /* Set the kids to track the top left corner of the window when resized.
2725 Set the NSText to track the bottom right corner as well.
2727 for (i = 0; i < nkids; i++) {
2728 NSView *kid = [kids objectAtIndex:i];
2729 unsigned long mask = NSViewMaxXMargin | NSViewMinYMargin;
2730 if ([kid isKindOfClass:[NSText class]])
2731 mask |= NSViewWidthSizable|NSViewHeightSizable;
2732 [kid setAutoresizingMask:mask];
2735 # endif // !USE_IPHONE
2741 wrap_with_buttons (NSWindow *window, NSView *panel)
2745 // Make a box to hold the buttons at the bottom of the window.
2747 rect = [panel frame];
2748 rect.origin.x = rect.origin.y = 0;
2749 rect.size.height = 10;
2750 NSBox *bbox = [[NSBox alloc] initWithFrame:rect];
2751 [bbox setTitlePosition:NSNoTitle];
2752 [bbox setBorderType:NSNoBorder];
2754 // Make some buttons: Default, Cancel, OK
2756 rect.origin.x = rect.origin.y = 0;
2757 rect.size.width = rect.size.height = 10;
2758 NSButton *reset = [[NSButton alloc] initWithFrame:rect];
2759 [reset setTitle:@"Reset to Defaults"];
2760 [reset setBezelStyle:NSRoundedBezelStyle];
2763 rect = [reset frame];
2764 NSButton *ok = [[NSButton alloc] initWithFrame:rect];
2765 [ok setTitle:@"OK"];
2766 [ok setBezelStyle:NSRoundedBezelStyle];
2768 rect = [bbox frame];
2769 rect.origin.x = rect.size.width - [ok frame].size.width;
2770 [ok setFrameOrigin:rect.origin];
2773 NSButton *cancel = [[NSButton alloc] initWithFrame:rect];
2774 [cancel setTitle:@"Cancel"];
2775 [cancel setBezelStyle:NSRoundedBezelStyle];
2777 rect.origin.x -= [cancel frame].size.width + 10;
2778 [cancel setFrameOrigin:rect.origin];
2780 // Bind OK to RET and Cancel to ESC.
2781 [ok setKeyEquivalent:@"\r"];
2782 [cancel setKeyEquivalent:@"\e"];
2784 // The correct width for OK and Cancel buttons is 68 pixels
2785 // ("Human Interface Guidelines: Controls: Buttons:
2786 // Push Button Specifications").
2789 rect.size.width = 68;
2792 rect = [cancel frame];
2793 rect.size.width = 68;
2794 [cancel setFrame:rect];
2796 // It puts the buttons in the box or else it gets the hose again
2798 [bbox addSubview:ok];
2799 [bbox addSubview:cancel];
2800 [bbox addSubview:reset];
2803 // make a box to hold the button-box, and the preferences view
2805 rect = [bbox frame];
2806 rect.origin.y += rect.size.height;
2807 NSBox *pbox = [[NSBox alloc] initWithFrame:rect];
2808 [pbox setTitlePosition:NSNoTitle];
2809 [pbox setBorderType:NSBezelBorder];
2811 // Enforce a max height on the dialog, so that it's obvious to me
2812 // (on a big screen) when the dialog will fall off the bottom of
2813 // a small screen (e.g., 1024x768 laptop with a huge bottom dock).
2815 NSRect f = [panel frame];
2816 int screen_height = (768 // shortest "modern" Mac display
2818 - 56 // System Preferences toolbar
2819 - 140 // default magnified bottom dock icon
2821 if (f.size.height > screen_height) {
2822 NSLog(@"%@ height was %.0f; clipping to %d",
2823 [panel class], f.size.height, screen_height);
2824 f.size.height = screen_height;
2829 [pbox addSubview:panel];
2830 [pbox addSubview:bbox];
2833 [reset setAutoresizingMask:NSViewMaxXMargin];
2834 [cancel setAutoresizingMask:NSViewMinXMargin];
2835 [ok setAutoresizingMask:NSViewMinXMargin];
2836 [bbox setAutoresizingMask:NSViewWidthSizable];
2840 [ok setTarget:window];
2841 [cancel setTarget:window];
2842 [reset setTarget:window];
2843 [ok setAction:@selector(okAction:)];
2844 [cancel setAction:@selector(cancelAction:)];
2845 [reset setAction:@selector(resetAction:)];
2849 #endif // !USE_IPHONE
2852 /* Iterate over and process the children of the root node of the XML document.
2854 - (void)traverseTree
2857 NSView *parent = [self view];
2859 NSWindow *parent = self;
2861 NSXMLNode *node = xml_root;
2863 if (![[node name] isEqualToString:@"screensaver"]) {
2864 NSAssert (0, @"top level node is not <xscreensaver>");
2867 saver_name = [self parseXScreenSaverTag: node];
2868 saver_name = [saver_name stringByReplacingOccurrencesOfString:@" "
2870 [saver_name retain];
2875 rect.origin.x = rect.origin.y = 0;
2876 rect.size.width = rect.size.height = 1;
2878 NSView *panel = [[NSView alloc] initWithFrame:rect];
2879 [self traverseChildren:node on:panel];
2880 fix_contentview_size (panel);
2882 NSView *root = wrap_with_buttons (parent, panel);
2883 [userDefaultsController setAppliesImmediately:NO];
2885 [panel setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
2887 rect = [parent frameRectForContentRect:[root frame]];
2888 [parent setFrame:rect display:NO];
2889 [parent setMinSize:rect.size];
2891 [parent setContentView:root];
2893 # else // USE_IPHONE
2895 CGRect r = [parent frame];
2896 r.size = [[UIScreen mainScreen] bounds].size;
2897 [parent setFrame:r];
2898 [self traverseChildren:node on:parent];
2900 # endif // USE_IPHONE
2904 - (void)parser:(NSXMLParser *)parser
2905 didStartElement:(NSString *)elt
2906 namespaceURI:(NSString *)ns
2907 qualifiedName:(NSString *)qn
2908 attributes:(NSDictionary *)attrs
2910 NSXMLElement *e = [[NSXMLElement alloc] initWithName:elt];
2911 [e setKind:SimpleXMLElementKind];
2912 [e setAttributesAsDictionary:attrs];
2913 NSXMLElement *p = xml_parsing;
2917 xml_root = xml_parsing;
2920 - (void)parser:(NSXMLParser *)parser
2921 didEndElement:(NSString *)elt
2922 namespaceURI:(NSString *)ns
2923 qualifiedName:(NSString *)qn
2925 NSXMLElement *p = xml_parsing;
2927 NSLog(@"extra close: %@", elt);
2928 } else if (![[p name] isEqualToString:elt]) {
2929 NSLog(@"%@ closed by %@", [p name], elt);
2931 NSXMLElement *n = xml_parsing;
2932 xml_parsing = [n parent];
2937 - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
2939 NSXMLElement *e = [[NSXMLElement alloc] initWithName:@"text"];
2940 [e setKind:SimpleXMLTextKind];
2941 NSXMLElement *p = xml_parsing;
2943 [e setObjectValue: string];
2948 # ifdef USE_PICKER_VIEW
2950 #pragma mark UIPickerView delegate methods
2952 - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pv
2954 return 1; // Columns
2957 - (NSInteger)pickerView:(UIPickerView *)pv
2958 numberOfRowsInComponent:(NSInteger)column
2960 NSAssert (column == 0, @"weird column");
2961 NSArray *a = [picker_values objectAtIndex: [pv tag]];
2962 if (! a) return 0; // Too early?
2966 - (CGFloat)pickerView:(UIPickerView *)pv
2967 rowHeightForComponent:(NSInteger)column
2972 - (CGFloat)pickerView:(UIPickerView *)pv
2973 widthForComponent:(NSInteger)column
2975 NSAssert (column == 0, @"weird column");
2976 NSArray *a = [picker_values objectAtIndex: [pv tag]];
2977 if (! a) return 0; // Too early?
2979 UIFont *f = [UIFont systemFontOfSize:[NSFont systemFontSize]];
2981 for (NSArray *a2 in a) {
2982 NSString *s = [a2 objectAtIndex:0];
2983 CGSize r = [s sizeWithFont:f];
2984 if (r.width > max) max = r.width;
2987 max *= 1.7; // WTF!!
2999 - (NSString *)pickerView:(UIPickerView *)pv
3000 titleForRow:(NSInteger)row
3001 forComponent:(NSInteger)column
3003 NSAssert (column == 0, @"weird column");
3004 NSArray *a = [picker_values objectAtIndex: [pv tag]];
3005 if (! a) return 0; // Too early?
3006 a = [a objectAtIndex:row];
3007 NSAssert (a, @"internal error");
3008 return [a objectAtIndex:0];
3011 # endif // USE_PICKER_VIEW
3014 #pragma mark UITableView delegate methods
3016 - (void) addResetButton
3018 [[self navigationItem]
3019 setRightBarButtonItem: [[UIBarButtonItem alloc]
3020 initWithTitle: @"Reset to Defaults"
3021 style: UIBarButtonItemStyleBordered
3023 action:@selector(resetAction:)]];
3024 NSString *s = saver_name;
3025 if ([self view].frame.size.width > 320)
3026 s = [s stringByAppendingString: @" Settings"];
3027 [self navigationItem].title = s;
3031 - (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)o
3036 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tv {
3037 // Number of vertically-stacked white boxes.
3038 return [controls count];
3041 - (NSInteger)tableView:(UITableView *)tableView
3042 numberOfRowsInSection:(NSInteger)section
3044 // Number of lines in each vertically-stacked white box.
3045 NSAssert (controls, @"internal error");
3046 return [[controls objectAtIndex:section] count];
3049 - (NSString *)tableView:(UITableView *)tv
3050 titleForHeaderInSection:(NSInteger)section
3052 // Titles above each vertically-stacked white box.
3053 // if (section == 0)
3054 // return [saver_name stringByAppendingString:@" Settings"];
3059 - (CGFloat)tableView:(UITableView *)tv
3060 heightForRowAtIndexPath:(NSIndexPath *)ip
3062 CGFloat h = [tv rowHeight];
3064 NSView *ctl = [[controls objectAtIndex:[ip indexAtPosition:0]]
3065 objectAtIndex:[ip indexAtPosition:1]];
3067 if ([ctl isKindOfClass:[NSArray class]]) {
3068 NSArray *set = (NSArray *) ctl;
3069 switch ([set count]) {
3071 # ifdef LABEL_ABOVE_SLIDER
3072 h *= 1.7; break; // label + left/slider/right: 2 1/2 lines
3074 case 3: h *= 1.2; break; // left/slider/right: 1 1/2 lines
3076 if ([[set objectAtIndex:1] isKindOfClass:[UITextField class]])
3080 } else if ([ctl isKindOfClass:[UILabel class]]) {
3081 UILabel *t = (UILabel *) ctl;
3083 r.size.width = 250; // WTF! Black magic!
3084 r.size.width -= LEFT_MARGIN;
3088 h = r.size.height + LINE_SPACING * 3;
3089 # ifdef USE_HTML_LABELS
3091 } else if ([ctl isKindOfClass:[HTMLLabel class]]) {
3093 HTMLLabel *t = (HTMLLabel *) ctl;
3095 r.size.width = [tv frame].size.width;
3096 r.size.width -= LEFT_MARGIN * 2;
3100 h = r.size.height + LINE_SPACING * 3;
3102 # endif // USE_HTML_LABELS
3104 CGFloat h2 = [ctl frame].size.height;
3105 h2 += LINE_SPACING * 2;
3113 - (void)refreshTableView
3115 UITableView *tv = (UITableView *) [self view];
3116 NSMutableArray *a = [NSMutableArray arrayWithCapacity:20];
3117 int rows = [self numberOfSectionsInTableView:tv];
3118 for (int i = 0; i < rows; i++) {
3119 int cols = [self tableView:tv numberOfRowsInSection:i];
3120 for (int j = 0; j < cols; j++) {
3124 [a addObject: [NSIndexPath indexPathWithIndexes:ip length:2]];
3129 [tv reloadRowsAtIndexPaths:a withRowAnimation:UITableViewRowAnimationNone];
3134 - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)o
3136 [NSTimer scheduledTimerWithTimeInterval: 0
3138 selector:@selector(refreshTableView)
3144 #ifndef USE_PICKER_VIEW
3146 - (void)updateRadioGroupCell:(UITableViewCell *)cell
3147 button:(RadioButton *)b
3149 NSArray *item = [[b items] objectAtIndex: [b index]];
3150 NSString *pref_key = [item objectAtIndex:1];
3151 NSObject *pref_val = [item objectAtIndex:2];
3152 NSObject *current = [userDefaultsController objectForKey:pref_key];
3154 // Convert them both to strings and compare those, so that
3155 // we don't get screwed by int 1 versus string "1".
3156 // Will boolean true/1 screw us here too?
3158 NSString *pref_str = ([pref_val isKindOfClass:[NSString class]]
3159 ? (NSString *) pref_val
3160 : [(NSNumber *) pref_val stringValue]);
3161 NSString *current_str = ([current isKindOfClass:[NSString class]]
3162 ? (NSString *) current
3163 : [(NSNumber *) current stringValue]);
3164 BOOL match_p = [current_str isEqualToString:pref_str];
3166 // NSLog(@"\"%@\" = \"%@\" | \"%@\" ", pref_key, pref_val, current_str);
3169 [cell setAccessoryType:UITableViewCellAccessoryCheckmark];
3171 [cell setAccessoryType:UITableViewCellAccessoryNone];
3175 - (void)tableView:(UITableView *)tv
3176 didSelectRowAtIndexPath:(NSIndexPath *)ip
3178 RadioButton *ctl = [[controls objectAtIndex:[ip indexAtPosition:0]]
3179 objectAtIndex:[ip indexAtPosition:1]];
3180 if (! [ctl isKindOfClass:[RadioButton class]])
3183 [self radioAction:ctl];
3184 [self refreshTableView];
3188 #endif // !USE_PICKER_VIEW
3192 - (UITableViewCell *)tableView:(UITableView *)tv
3193 cellForRowAtIndexPath:(NSIndexPath *)ip
3196 /* #### If we re-use cells, then clicking on a checkbox RadioButton
3197 (in non-USE_PICKER_VIEW mode) makes all the cells disappear.
3198 This doesn't happen if we don't re-use any cells. Oh well.
3200 NSString *id = [NSString stringWithFormat: @"%d:%d",
3201 [ip indexAtPosition:0],
3202 [ip indexAtPosition:1]];
3203 UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier: id];
3205 if (cell) return cell;
3208 UITableViewCell *cell;
3211 cell = [[[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault
3212 reuseIdentifier: id]
3214 cell.selectionStyle = UITableViewCellSelectionStyleNone;
3216 CGRect p = [cell frame];
3219 p.size.height = [self tableView:tv heightForRowAtIndexPath:ip];
3222 // Allocate more space to the controls on iPad screens,
3223 // and on landscape-mode iPhones.
3224 CGFloat ww = [tv frame].size.width;
3225 CGFloat left_edge = (ww > 700
3226 ? p.size.width * 0.9
3228 ? p.size.width * 0.5
3229 : p.size.width * 0.3);
3230 CGFloat right_edge = p.origin.x + p.size.width - LEFT_MARGIN;
3233 NSView *ctl = [[controls objectAtIndex:[ip indexAtPosition:0]]
3234 objectAtIndex:[ip indexAtPosition:1]];
3236 if ([ctl isKindOfClass:[NSArray class]]) {
3237 // This cell has a set of objects in it.
3238 NSArray *set = (NSArray *) ctl;
3239 switch ([set count]) {
3242 // With 2 elements, the first of the pair must be a label.
3243 UILabel *label = (UILabel *) [set objectAtIndex: 0];
3244 NSAssert ([label isKindOfClass:[UILabel class]], @"unhandled type");
3245 ctl = [set objectAtIndex: 1];
3248 if ([ctl isKindOfClass:[UISwitch class]]) {
3249 // Flush right checkboxes.
3250 ctl.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
3251 r.size.width = 80; // Magic.
3252 r.origin.x = right_edge - r.size.width;
3254 // Expandable sliders.
3255 ctl.autoresizingMask = UIViewAutoresizingFlexibleWidth;
3256 r.origin.x = left_edge;
3257 r.size.width = right_edge - r.origin.x;
3259 r.origin.y = (p.size.height - r.size.height) / 2;
3263 NSView *box = [[UIView alloc] initWithFrame:p];
3264 [box addSubview: ctl];
3266 // cell.textLabel.text = [(UILabel *) ctl text];
3268 r.origin.x = LEFT_MARGIN;
3270 r.size.width = [ctl frame].origin.x - r.origin.x;
3271 r.size.height = p.size.height;
3273 [label setFont:[NSFont boldSystemFontOfSize: FONT_SIZE]];
3274 label.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
3275 box. autoresizingMask = UIViewAutoresizingFlexibleWidth;
3276 [box addSubview: label];
3284 // With 3 elements, the first and last must be labels.
3285 // With 4 elements, the first, second and last must be labels.
3287 UILabel *top = ([set count] == 4
3288 ? [set objectAtIndex: i++]
3290 UILabel *left = [set objectAtIndex: i++];
3291 NSView *mid = [set objectAtIndex: i++];
3292 UILabel *right = [set objectAtIndex: i++];
3293 NSAssert (!top || [top isKindOfClass:[UILabel class]], @"WTF");
3294 NSAssert ( [left isKindOfClass:[UILabel class]], @"WTF");
3295 NSAssert ( ![mid isKindOfClass:[UILabel class]], @"WTF");
3296 NSAssert ( [right isKindOfClass:[UILabel class]], @"WTF");
3298 // 3 elements: control at top of cell.
3299 // 4 elements: center the control vertically.
3301 # ifdef LABEL_ABOVE_SLIDER
3302 left_edge = LEFT_MARGIN;
3304 r.origin.x = left_edge;
3305 r.size.width = right_edge - r.origin.x;
3306 r.origin.y = ([set count] == 3
3308 : (p.size.height - r.size.height) / 2);
3311 // Top label goes above, flush center/top.
3313 r.size = [[top text] sizeWithFont:[top font]
3315 CGSizeMake (p.size.width - LEFT_MARGIN*2,
3317 lineBreakMode:[top lineBreakMode]];
3318 r.origin.x = (p.size.width - r.size.width) / 2;
3323 // Left label goes under control, flush left/bottom.
3324 r.size = [[left text] sizeWithFont:[left font]
3326 CGSizeMake(p.size.width - LEFT_MARGIN*2,
3328 lineBreakMode:[left lineBreakMode]];
3329 r.origin.x = [mid frame].origin.x;
3330 r.origin.y = p.size.height - r.size.height - 4;
3333 // Right label goes under control, flush right/bottom.
3335 r.size = [[right text] sizeWithFont:[right font]
3337 CGSizeMake(p.size.width - LEFT_MARGIN*2,
3339 lineBreakMode:[right lineBreakMode]];
3340 r.origin.x = ([mid frame].origin.x + [mid frame].size.width -
3342 r.origin.y = [left frame].origin.y;
3346 ctl = [[UIView alloc] initWithFrame:p];
3348 # ifdef LABEL_ABOVE_SLIDER
3349 [ctl addSubview: top];
3350 top.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin|
3351 UIViewAutoresizingFlexibleRightMargin);
3354 r.origin.x = LEFT_MARGIN;
3356 r.size.width = [mid frame].origin.x - r.origin.x;
3357 r.size.height = p.size.height;
3359 top.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
3360 [ctl addSubview: top];
3363 [ctl addSubview: left];
3364 [ctl addSubview: mid];
3365 [ctl addSubview: right];
3367 left. autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
3368 mid. autoresizingMask = UIViewAutoresizingFlexibleWidth;
3369 right.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
3370 ctl. autoresizingMask = UIViewAutoresizingFlexibleWidth;
3374 NSAssert (0, @"unhandled size");
3377 // A single view, not a pair.
3380 r.origin.x = LEFT_MARGIN;
3381 r.origin.y = LINE_SPACING;
3382 r.size.width = right_edge - r.origin.x;
3385 ctl.autoresizingMask = UIViewAutoresizingFlexibleWidth;
3387 # ifndef USE_PICKER_VIEW
3388 if ([ctl isKindOfClass:[RadioButton class]])
3389 [self updateRadioGroupCell:cell button:(RadioButton *)ctl];
3390 # endif // USE_PICKER_VIEW
3393 if ([ctl isKindOfClass:[UILabel class]]) {
3394 // Make label full height to allow text to line-wrap if necessary.
3396 r.origin.y = p.origin.y;
3397 r.size.height = p.size.height;
3401 [cell.contentView addSubview: ctl];
3405 # endif // USE_IPHONE
3408 /* When this object is instantiated, it parses the XML file and creates
3409 controls on itself that are hooked up to the appropriate preferences.
3410 The default size of the view is just big enough to hold them all.
3412 - (id)initWithXMLFile: (NSString *) xml_file
3413 options: (const XrmOptionDescRec *) _opts
3414 controller: (NSUserDefaultsController *) _prefs
3415 defaults: (NSDictionary *) _defs
3418 self = [super init];
3419 # else // !USE_IPHONE
3420 self = [super initWithStyle:UITableViewStyleGrouped];
3421 self.title = [saver_name stringByAppendingString:@" Settings"];
3422 # endif // !USE_IPHONE
3423 if (! self) return 0;
3425 // instance variables
3427 defaultOptions = _defs;
3428 userDefaultsController = _prefs;
3429 [userDefaultsController retain];
3431 NSURL *furl = [NSURL fileURLWithPath:xml_file];
3434 NSAssert1 (0, @"can't URLify \"%@\"", xml_file);
3438 #if 0 // -- the old way
3440 NSXMLDocument *xmlDoc = [[NSXMLDocument alloc]
3441 initWithContentsOfURL:furl
3442 options:(NSXMLNodePreserveWhitespace |
3443 NSXMLNodePreserveCDATA)
3445 if (!xmlDoc || err) {
3447 NSAssert2 (0, @"XML Error: %@: %@",
3448 xml_file, [err localizedDescription]);
3452 traverse_tree (prefs, self, opts, [xmlDoc rootElement]);
3456 NSXMLParser *xmlDoc = [[NSXMLParser alloc] initWithContentsOfURL:furl];
3458 NSAssert1 (0, @"XML Error: %@", xml_file);
3461 [xmlDoc setDelegate:self];
3462 if (! [xmlDoc parse]) {
3463 NSError *err = [xmlDoc parserError];
3464 NSAssert2 (0, @"XML Error: %@: %@", xml_file, err);
3468 [self traverseTree];
3472 [self addResetButton];
3481 [saver_name release];
3482 [userDefaultsController release];
3485 [pref_keys release];
3486 [pref_ctls release];
3487 # ifdef USE_PICKER_VIEW
3488 [picker_values release];