diff options
Diffstat (limited to 'OSX/XScreenSaverConfigSheet.m')
-rw-r--r-- | OSX/XScreenSaverConfigSheet.m | 3710 |
1 files changed, 0 insertions, 3710 deletions
diff --git a/OSX/XScreenSaverConfigSheet.m b/OSX/XScreenSaverConfigSheet.m deleted file mode 100644 index eaa5add..0000000 --- a/OSX/XScreenSaverConfigSheet.m +++ /dev/null @@ -1,3710 +0,0 @@ -/* xscreensaver, Copyright (c) 2006-2017 Jamie Zawinski <jwz@jwz.org> - * - * Permission to use, copy, modify, distribute, and sell this software and its - * documentation for any purpose is hereby granted without fee, provided that - * the above copyright notice appear in all copies and that both that - * copyright notice and this permission notice appear in supporting - * documentation. No representations are made about the suitability of this - * software for any purpose. It is provided "as is" without express or - * implied warranty. - */ - -/* XScreenSaver uses XML files to describe the user interface for configuring - the various screen savers. These files live in .../hacks/config/ and - say relatively high level things like: "there should be a checkbox - labelled "Leave Trails", and when it is checked, add the option '-trails' - to the command line when launching the program." - - This code reads that XML and constructs a Cocoa interface from it. - The Cocoa controls are hooked up to NSUserDefaultsController to save - those settings into the MacOS preferences system. The Cocoa preferences - names are the same as the resource names specified in the screenhack's - 'options' array (we use that array to map the command line switches - specified in the XML to the resource names to use). - */ - -#import "XScreenSaverConfigSheet.h" -#import "Updater.h" - -#import "jwxyz.h" -#import "InvertedSlider.h" - -#ifdef USE_IPHONE -# define NSView UIView -# define NSRect CGRect -# define NSSize CGSize -# define NSTextField UITextField -# define NSButton UIButton -# define NSFont UIFont -# define NSStepper UIStepper -# define NSMenuItem UIMenuItem -# define NSText UILabel -# define minValue minimumValue -# define maxValue maximumValue -# define setMinValue setMinimumValue -# define setMaxValue setMaximumValue -# define LABEL UILabel -#else -# define LABEL NSTextField -#endif // USE_IPHONE - -#undef LABEL_ABOVE_SLIDER -#define USE_HTML_LABELS - - -#pragma mark XML Parser - -/* I used to use the "NSXMLDocument" XML parser, but that doesn't exist - on iOS. The "NSXMLParser" parser exists on both OSX and iOS, so I - converted to use that. However, to avoid having to re-write all of - the old code, I faked out a halfassed implementation of the - "NSXMLNode" class that "NSXMLDocument" used to return. - */ - -#define NSXMLNode SimpleXMLNode -#define NSXMLElement SimpleXMLNode -#define NSXMLCommentKind SimpleXMLCommentKind -#define NSXMLElementKind SimpleXMLElementKind -#define NSXMLAttributeKind SimpleXMLAttributeKind -#define NSXMLTextKind SimpleXMLTextKind - -typedef enum { SimpleXMLCommentKind, - SimpleXMLElementKind, - SimpleXMLAttributeKind, - SimpleXMLTextKind, -} SimpleXMLKind; - -@interface SimpleXMLNode : NSObject -{ - SimpleXMLKind kind; - NSString *name; - SimpleXMLNode *parent; - NSMutableArray *children; - NSMutableArray *attributes; - id object; -} - -@property(nonatomic) SimpleXMLKind kind; -@property(nonatomic, retain) NSString *name; -@property(nonatomic, retain) SimpleXMLNode *parent; -@property(nonatomic, retain) NSMutableArray *children; -@property(nonatomic, retain) NSMutableArray *attributes; -@property(nonatomic, retain, getter=objectValue, setter=setObjectValue:) - id object; - -@end - -@implementation SimpleXMLNode - -@synthesize kind; -@synthesize name; -//@synthesize parent; -@synthesize children; -@synthesize attributes; -@synthesize object; - -- (id) init -{ - self = [super init]; - attributes = [NSMutableArray arrayWithCapacity:10]; - return self; -} - - -- (id) initWithName:(NSString *)n -{ - self = [self init]; - [self setKind:NSXMLElementKind]; - [self setName:n]; - return self; -} - - -- (void) setAttributesAsDictionary:(NSDictionary *)dict -{ - for (NSString *key in dict) { - NSObject *val = [dict objectForKey:key]; - SimpleXMLNode *n = [[SimpleXMLNode alloc] init]; - [n setKind:SimpleXMLAttributeKind]; - [n setName:key]; - [n setObjectValue:val]; - [attributes addObject:n]; - [n release]; - } -} - -- (SimpleXMLNode *) parent { return parent; } - -- (void) setParent:(SimpleXMLNode *)p -{ - NSAssert (!parent, @"parent already set"); - if (!p) return; - parent = p; - NSMutableArray *kids = [p children]; - if (!kids) { - kids = [NSMutableArray arrayWithCapacity:10]; - [p setChildren:kids]; - } - [kids addObject:self]; -} -@end - - -#pragma mark textMode value transformer - -// A value transformer for mapping "url" to "3" and vice versa in the -// "textMode" preference, since NSMatrix uses NSInteger selectedIndex. - -#ifndef USE_IPHONE -@interface TextModeTransformer: NSValueTransformer {} -@end -@implementation TextModeTransformer -+ (Class)transformedValueClass { return [NSString class]; } -+ (BOOL)allowsReverseTransformation { return YES; } - -- (id)transformedValue:(id)value { - if ([value isKindOfClass:[NSString class]]) { - int i = -1; - if ([value isEqualToString:@"date"]) { i = 0; } - else if ([value isEqualToString:@"literal"]) { i = 1; } - else if ([value isEqualToString:@"file"]) { i = 2; } - else if ([value isEqualToString:@"url"]) { i = 3; } - else if ([value isEqualToString:@"program"]) { i = 4; } - if (i != -1) - value = [NSNumber numberWithInt: i]; - } - return value; -} - -- (id)reverseTransformedValue:(id)value { - if ([value isKindOfClass:[NSNumber class]]) { - switch ((int) [value doubleValue]) { - case 0: value = @"date"; break; - case 1: value = @"literal"; break; - case 2: value = @"file"; break; - case 3: value = @"url"; break; - case 4: value = @"program"; break; - } - } - return value; -} -@end -#endif // USE_IPHONE - - -#pragma mark Implementing radio buttons - -/* The UIPickerView is a hideous and uncustomizable piece of shit. - I can't believe Apple actually released that thing on the world. - Let's fake up some radio buttons instead. - */ - -#if defined(USE_IPHONE) && !defined(USE_PICKER_VIEW) - -@interface RadioButton : UILabel -{ - int index; - NSArray *items; -} - -@property(nonatomic) int index; -@property(nonatomic, retain) NSArray *items; - -@end - -@implementation RadioButton - -@synthesize index; -@synthesize items; - -- (id) initWithIndex:(int)_index items:_items -{ - self = [super initWithFrame:CGRectZero]; - index = _index; - items = [_items retain]; - - [self setText: [[items objectAtIndex:index] objectAtIndex:0]]; - [self setBackgroundColor:[UIColor clearColor]]; - [self sizeToFit]; - - return self; -} - -@end - - -# endif // !USE_PICKER_VIEW - - -# pragma mark Implementing labels with clickable links - -#if defined(USE_IPHONE) && defined(USE_HTML_LABELS) - -@interface HTMLLabel : UIView <UIWebViewDelegate> -{ - NSString *html; - UIFont *font; - UIWebView *webView; -} - -@property(nonatomic, retain) NSString *html; -@property(nonatomic, retain) UIWebView *webView; - -- (id) initWithHTML:(NSString *)h font:(UIFont *)f; -- (id) initWithText:(NSString *)t font:(UIFont *)f; -- (void) setHTML:(NSString *)h; -- (void) setText:(NSString *)t; -- (void) sizeToFit; - -@end - -@implementation HTMLLabel - -@synthesize html; -@synthesize webView; - -- (id) initWithHTML:(NSString *)h font:(UIFont *)f -{ - self = [super init]; - if (! self) return 0; - font = [f retain]; - webView = [[UIWebView alloc] init]; - webView.delegate = self; - webView.dataDetectorTypes = UIDataDetectorTypeNone; - self. autoresizingMask = UIViewAutoresizingNone; // we do it manually - webView.autoresizingMask = UIViewAutoresizingNone; - webView.scrollView.scrollEnabled = NO; - webView.scrollView.bounces = NO; - webView.opaque = NO; - [webView setBackgroundColor:[UIColor clearColor]]; - - [self addSubview: webView]; - [self setHTML: h]; - return self; -} - -- (id) initWithText:(NSString *)t font:(UIFont *)f -{ - self = [self initWithHTML:@"" font:f]; - if (! self) return 0; - [self setText: t]; - return self; -} - - -- (void) setHTML: (NSString *)h -{ - if (! h) return; - [h retain]; - if (html) [html release]; - html = h; - NSString *h2 = - [NSString stringWithFormat: - @"<!DOCTYPE HTML PUBLIC " - "\"-//W3C//DTD HTML 4.01 Transitional//EN\"" - " \"http://www.w3.org/TR/html4/loose.dtd\">" - "<HTML>" - "<HEAD>" -// "<META NAME=\"viewport\" CONTENT=\"" -// "width=device-width" -// "initial-scale=1.0;" -// "maximum-scale=1.0;\">" - "<STYLE>" - "<!--\n" - "body {" - " margin: 0; padding: 0; border: 0;" - " font-family: \"%@\";" - " font-size: %.4fpx;" // Must be "px", not "pt"! - " line-height: %.4fpx;" // And no spaces before it. - " -webkit-text-size-adjust: none;" - "}" - "\n//-->\n" - "</STYLE>" - "</HEAD>" - "<BODY>" - "%@" - "</BODY>" - "</HTML>", - [font fontName], - [font pointSize], - [font lineHeight], - h]; - [webView stopLoading]; - [webView loadHTMLString:h2 baseURL:[NSURL URLWithString:@""]]; -} - - -static char *anchorize (const char *url); - -- (void) setText: (NSString *)t -{ - t = [t stringByTrimmingCharactersInSet:[NSCharacterSet - whitespaceCharacterSet]]; - t = [t stringByReplacingOccurrencesOfString:@"&" withString:@"&"]; - t = [t stringByReplacingOccurrencesOfString:@"<" withString:@"<"]; - t = [t stringByReplacingOccurrencesOfString:@">" withString:@">"]; - t = [t stringByReplacingOccurrencesOfString:@"\n\n" withString:@" <P> "]; - t = [t stringByReplacingOccurrencesOfString:@"<P> " - withString:@"<P> "]; - t = [t stringByReplacingOccurrencesOfString:@"\n " - withString:@"<BR> "]; - - NSString *h = @""; - for (NSString *s in - [t componentsSeparatedByCharactersInSet: - [NSCharacterSet whitespaceAndNewlineCharacterSet]]) { - if ([s hasPrefix:@"http://"] || - [s hasPrefix:@"https://"]) { - char *anchor = anchorize ([s cStringUsingEncoding:NSUTF8StringEncoding]); - NSString *a2 = [NSString stringWithCString: anchor - encoding: NSUTF8StringEncoding]; - s = [NSString stringWithFormat: @"<A HREF=\"%@\">%@</A><BR>", s, a2]; - free (anchor); - } - h = [NSString stringWithFormat: @"%@ %@", h, s]; - } - - h = [h stringByReplacingOccurrencesOfString:@" <P> " withString:@"<P>"]; - h = [h stringByReplacingOccurrencesOfString:@"<BR><P>" withString:@"<P>"]; - h = [h stringByTrimmingCharactersInSet:[NSCharacterSet - whitespaceAndNewlineCharacterSet]]; - - [self setHTML: h]; -} - - --(BOOL) webView:(UIWebView *)wv - shouldStartLoadWithRequest:(NSURLRequest *)req - navigationType:(UIWebViewNavigationType)type -{ - // Force clicked links to open in Safari, not in this window. - if (type == UIWebViewNavigationTypeLinkClicked) { - [[UIApplication sharedApplication] openURL:[req URL]]; - return NO; - } - return YES; -} - - -- (void) setFrame: (CGRect)r -{ - [super setFrame: r]; - r.origin.x = 0; - r.origin.y = 0; - [webView setFrame: r]; -} - - -- (NSString *) stripTags:(NSString *)str -{ - NSString *result = @""; - - // Add newlines. - str = [str stringByReplacingOccurrencesOfString:@"<P>" - withString:@"<BR><BR>" - options:NSCaseInsensitiveSearch - range:NSMakeRange(0, [str length])]; - str = [str stringByReplacingOccurrencesOfString:@"<BR>" - withString:@"\n" - options:NSCaseInsensitiveSearch - range:NSMakeRange(0, [str length])]; - - // Remove HREFs. - for (NSString *s in [str componentsSeparatedByString: @"<"]) { - NSRange r = [s rangeOfString:@">"]; - if (r.length > 0) - s = [s substringFromIndex: r.location + r.length]; - result = [result stringByAppendingString: s]; - } - - // Compress internal horizontal whitespace. - str = result; - result = @""; - for (NSString *s in [str componentsSeparatedByCharactersInSet: - [NSCharacterSet whitespaceCharacterSet]]) { - if ([result length] == 0) - result = s; - else if ([s length] > 0) - result = [NSString stringWithFormat: @"%@ %@", result, s]; - } - - return result; -} - - -- (void) sizeToFit -{ - CGRect r = [self frame]; - - /* It would be sensible to just ask the UIWebView how tall the page is, - instead of hoping that NSString and UIWebView measure fonts and do - wrapping in exactly the same way, but since UIWebView is asynchronous, - we'd have to wait for the document to load first, e.g.: - - - Start the document loading; - - return a default height to use for the UITableViewCell; - - wait for the webViewDidFinishLoad delegate method to fire; - - then force the UITableView to reload, to pick up the new height. - - But I couldn't make that work. - */ -# if 0 - r.size.height = [[webView - stringByEvaluatingJavaScriptFromString: - @"document.body.offsetHeight"] - doubleValue]; -# else - NSString *text = [self stripTags: html]; - CGSize s = r.size; - s.height = 999999; - s = [text boundingRectWithSize:s - options:NSStringDrawingUsesLineFragmentOrigin - attributes:@{NSFontAttributeName: font} - context:nil].size; - r.size.height = s.height; -# endif - - [self setFrame: r]; -} - - -- (void) dealloc -{ - [html release]; - [font release]; - [webView release]; - [super dealloc]; -} - -@end - -#endif // USE_IPHONE && USE_HTML_LABELS - - -@interface XScreenSaverConfigSheet (Private) - -- (void)traverseChildren:(NSXMLNode *)node on:(NSView *)parent; - -# ifndef USE_IPHONE -- (void) placeChild: (NSView *)c on:(NSView *)p right:(BOOL)r; -- (void) placeChild: (NSView *)c on:(NSView *)p; -static NSView *last_child (NSView *parent); -static void layout_group (NSView *group, BOOL horiz_p); -# else // USE_IPHONE -- (void) placeChild: (NSObject *)c on:(NSView *)p right:(BOOL)r; -- (void) placeChild: (NSObject *)c on:(NSView *)p; -- (void) placeSeparator; -- (void) bindResource:(NSObject *)ctl key:(NSString *)k reload:(BOOL)r; -- (void) refreshTableView; -# endif // USE_IPHONE - -@end - - -@implementation XScreenSaverConfigSheet - -# define LEFT_MARGIN 20 // left edge of window -# define COLUMN_SPACING 10 // gap between e.g. labels and text fields -# define LEFT_LABEL_WIDTH 70 // width of all left labels -# define LINE_SPACING 10 // leading between each line - -# define FONT_SIZE 17 // Magic hardcoded UITableView font size. - -#pragma mark Talking to the resource database - - -/* Normally we read resources by looking up "KEY" in the database - "org.jwz.xscreensaver.SAVERNAME". But in the all-in-one iPhone - app, everything is stored in the database "org.jwz.xscreensaver" - instead, so transform keys to "SAVERNAME.KEY". - - NOTE: This is duplicated in PrefsReader.m, cause I suck. - */ -- (NSString *) makeKey:(NSString *)key -{ -# ifdef USE_IPHONE - NSString *prefix = [saver_name stringByAppendingString:@"."]; - if (! [key hasPrefix:prefix]) // Don't double up! - key = [prefix stringByAppendingString:key]; -# endif - return key; -} - - -- (NSString *) makeCKey:(const char *)key -{ - return [self makeKey:[NSString stringWithCString:key - encoding:NSUTF8StringEncoding]]; -} - - -/* Given a command-line option, returns the corresponding resource name. - Any arguments in the switch string are ignored (e.g., "-foo x"). - */ -- (NSString *) switchToResource:(NSString *)cmdline_switch - opts:(const XrmOptionDescRec *)opts_array - valRet:(NSString **)val_ret -{ - char buf[1280]; - char *tail = 0; - NSAssert(cmdline_switch, @"cmdline switch is null"); - if (! [cmdline_switch getCString:buf maxLength:sizeof(buf) - encoding:NSUTF8StringEncoding]) { - NSAssert1(0, @"unable to convert %@", cmdline_switch); - return 0; - } - char *s = strpbrk(buf, " \t\r\n"); - if (s && *s) { - *s = 0; - tail = s+1; - while (*tail && (*tail == ' ' || *tail == '\t')) - tail++; - } - - while (opts_array[0].option) { - if (!strcmp (opts_array[0].option, buf)) { - const char *ret = 0; - - if (opts_array[0].argKind == XrmoptionNoArg) { - if (tail && *tail) - NSAssert1 (0, @"expected no args to switch: \"%@\"", - cmdline_switch); - ret = opts_array[0].value; - } else { - if (!tail || !*tail) - NSAssert1 (0, @"expected args to switch: \"%@\"", - cmdline_switch); - ret = tail; - } - - if (val_ret) - *val_ret = (ret - ? [NSString stringWithCString:ret - encoding:NSUTF8StringEncoding] - : 0); - - const char *res = opts_array[0].specifier; - while (*res && (*res == '.' || *res == '*')) - res++; - return [self makeCKey:res]; - } - opts_array++; - } - - NSAssert1 (0, @"\"%@\" not present in options", cmdline_switch); - return 0; -} - - -- (NSUserDefaultsController *)controllerForKey:(NSString *)key -{ - static NSDictionary *a = 0; - if (! a) { - a = UPDATER_DEFAULTS; - [a retain]; - } - if ([a objectForKey:key]) - // These preferences are global to all xscreensavers. - return globalDefaultsController; - else - // All other preferences are per-saver. - return userDefaultsController; -} - - -#ifdef USE_IPHONE - -// Called when a slider is bonked. -// -- (void)sliderAction:(UISlider*)sender -{ - if ([active_text_field canResignFirstResponder]) - [active_text_field resignFirstResponder]; - NSString *pref_key = [pref_keys objectAtIndex: [sender tag]]; - - // Hacky API. See comment in InvertedSlider.m. - double v = ([sender isKindOfClass: [InvertedSlider class]] - ? [(InvertedSlider *) sender transformedValue] - : [sender value]); - - [[self controllerForKey:pref_key] - setObject:((v == (int) v) - ? [NSNumber numberWithInt:(int) v] - : [NSNumber numberWithDouble: v]) - forKey:pref_key]; -} - -// Called when a checkbox/switch is bonked. -// -- (void)switchAction:(UISwitch*)sender -{ - if ([active_text_field canResignFirstResponder]) - [active_text_field resignFirstResponder]; - NSString *pref_key = [pref_keys objectAtIndex: [sender tag]]; - NSString *v = ([sender isOn] ? @"true" : @"false"); - [[self controllerForKey:pref_key] setObject:v forKey:pref_key]; -} - -# ifdef USE_PICKER_VIEW -// Called when a picker is bonked. -// -- (void)pickerView:(UIPickerView *)pv - didSelectRow:(NSInteger)row - inComponent:(NSInteger)column -{ - if ([active_text_field canResignFirstResponder]) - [active_text_field resignFirstResponder]; - - NSAssert (column == 0, @"internal error"); - NSArray *a = [picker_values objectAtIndex: [pv tag]]; - if (! a) return; // Too early? - a = [a objectAtIndex:row]; - NSAssert (a, @"missing row"); - -//NSString *label = [a objectAtIndex:0]; - NSString *pref_key = [a objectAtIndex:1]; - NSObject *pref_val = [a objectAtIndex:2]; - [[self controllerForKey:pref_key] setObject:pref_val forKey:pref_key]; -} -# else // !USE_PICKER_VIEW - -// Called when a RadioButton is bonked. -// -- (void)radioAction:(RadioButton*)sender -{ - if ([active_text_field canResignFirstResponder]) - [active_text_field resignFirstResponder]; - - NSArray *item = [[sender items] objectAtIndex: [sender index]]; - NSString *pref_key = [item objectAtIndex:1]; - NSObject *pref_val = [item objectAtIndex:2]; - [[self controllerForKey:pref_key] setObject:pref_val forKey:pref_key]; -} - -- (BOOL)textFieldShouldBeginEditing:(UITextField *)tf -{ - active_text_field = tf; - return YES; -} - -- (void)textFieldDidEndEditing:(UITextField *)tf -{ - NSString *pref_key = [pref_keys objectAtIndex: [tf tag]]; - NSString *txt = [tf text]; - [[self controllerForKey:pref_key] setObject:txt forKey:pref_key]; -} - -- (BOOL)textFieldShouldReturn:(UITextField *)tf -{ - active_text_field = nil; - [tf resignFirstResponder]; - return YES; -} - -# endif // !USE_PICKER_VIEW - -#endif // USE_IPHONE - - -# ifndef USE_IPHONE - -- (void) okAction:(NSObject *)arg -{ - // Without the setAppliesImmediately:, when the saver restarts, it's still - // got the old settings. -[XScreenSaverConfigSheet traverseTree] sets this - // to NO; default is YES. - - // #### However: I'm told that when these are set to YES, then changes to - // 'textLiteral', 'textURL' and 'textProgram' are ignored, but 'textFile' - // works. In StarWars, at least... - - [userDefaultsController setAppliesImmediately:YES]; - [globalDefaultsController setAppliesImmediately:YES]; - [userDefaultsController commitEditing]; - [globalDefaultsController commitEditing]; - [userDefaultsController save:self]; - [globalDefaultsController save:self]; - [NSApp endSheet:self returnCode:NSOKButton]; - [self close]; -} - -- (void) cancelAction:(NSObject *)arg -{ - [userDefaultsController revert:self]; - [globalDefaultsController revert:self]; - [NSApp endSheet:self returnCode:NSCancelButton]; - [self close]; -} -# endif // !USE_IPHONE - - -- (void) resetAction:(NSObject *)arg -{ -# ifndef USE_IPHONE - [userDefaultsController revertToInitialValues:self]; - [globalDefaultsController revertToInitialValues:self]; -# else // USE_IPHONE - - for (NSString *key in defaultOptions) { - NSObject *val = [defaultOptions objectForKey:key]; - [[self controllerForKey:key] setObject:val forKey:key]; - } - - for (UIControl *ctl in pref_ctls) { - NSString *pref_key = [pref_keys objectAtIndex: ctl.tag]; - [self bindResource:ctl key:pref_key reload:YES]; - } - - [self refreshTableView]; -# endif // USE_IPHONE -} - - -/* Connects a control (checkbox, etc) to the corresponding preferences key. - */ -- (void) bindResource:(NSObject *)control key:(NSString *)pref_key - reload:(BOOL)reload_p -{ - NSUserDefaultsController *prefs = [self controllerForKey:pref_key]; -# ifndef USE_IPHONE - NSDictionary *opts_dict = nil; - NSString *bindto = ([control isKindOfClass:[NSPopUpButton class]] - ? @"selectedObject" - : ([control isKindOfClass:[NSMatrix class]] - ? @"selectedIndex" - : @"value")); - - if ([control isKindOfClass:[NSMatrix class]]) { - opts_dict = @{ NSValueTransformerNameBindingOption: - @"TextModeTransformer" }; - } - - [control bind:bindto - toObject:prefs - withKeyPath:[@"values." stringByAppendingString: pref_key] - options:opts_dict]; - -# else // USE_IPHONE - SEL sel; - NSObject *val = [prefs objectForKey:pref_key]; - NSString *sval = 0; - double dval = 0; - - if ([val isKindOfClass:[NSString class]]) { - sval = (NSString *) val; - if (NSOrderedSame == [sval caseInsensitiveCompare:@"true"] || - NSOrderedSame == [sval caseInsensitiveCompare:@"yes"] || - NSOrderedSame == [sval caseInsensitiveCompare:@"1"]) - dval = 1; - else - dval = [sval doubleValue]; - } else if ([val isKindOfClass:[NSNumber class]]) { - // NSBoolean (__NSCFBoolean) is really NSNumber. - dval = [(NSNumber *) val doubleValue]; - sval = [(NSNumber *) val stringValue]; - } - - if ([control isKindOfClass:[UISlider class]]) { - sel = @selector(sliderAction:); - // Hacky API. See comment in InvertedSlider.m. - if ([control isKindOfClass:[InvertedSlider class]]) - [(InvertedSlider *) control setTransformedValue: dval]; - else - [(UISlider *) control setValue: dval]; - } else if ([control isKindOfClass:[UISwitch class]]) { - sel = @selector(switchAction:); - [(UISwitch *) control setOn: ((int) dval != 0)]; -# ifdef USE_PICKER_VIEW - } else if ([control isKindOfClass:[UIPickerView class]]) { - sel = 0; - [(UIPickerView *) control selectRow:((int)dval) inComponent:0 - animated:NO]; -# else // !USE_PICKER_VIEW - } else if ([control isKindOfClass:[RadioButton class]]) { - sel = 0; // radioAction: sent from didSelectRowAtIndexPath. - } else if ([control isKindOfClass:[UITextField class]]) { - sel = 0; // #### - [(UITextField *) control setText: sval]; -# endif // !USE_PICKER_VIEW - } else { - NSAssert (0, @"unknown class"); - } - - // NSLog(@"\"%@\" = \"%@\" [%@, %.1f]", pref_key, val, [val class], dval); - - if (!reload_p) { - if (! pref_keys) { - pref_keys = [[NSMutableArray arrayWithCapacity:10] retain]; - pref_ctls = [[NSMutableArray arrayWithCapacity:10] retain]; - } - - [pref_keys addObject: [self makeKey:pref_key]]; - [pref_ctls addObject: control]; - ((UIControl *) control).tag = [pref_keys count] - 1; - - if (sel) { - [(UIControl *) control addTarget:self action:sel - forControlEvents:UIControlEventValueChanged]; - } - } - -# endif // USE_IPHONE - -# if 0 - NSObject *def = [[prefs defaults] objectForKey:pref_key]; - NSString *s = [NSString stringWithFormat:@"bind: \"%@\"", pref_key]; - s = [s stringByPaddingToLength:18 withString:@" " startingAtIndex:0]; - s = [NSString stringWithFormat:@"%@ = %@", s, - ([def isKindOfClass:[NSString class]] - ? [NSString stringWithFormat:@"\"%@\"", def] - : def)]; - s = [s stringByPaddingToLength:30 withString:@" " startingAtIndex:0]; - s = [NSString stringWithFormat:@"%@ %@ / %@", s, - [def class], [control class]]; -# ifndef USE_IPHONE - s = [NSString stringWithFormat:@"%@ / %@", s, bindto]; -# endif - NSLog (@"%@", s); -# endif -} - - -- (void) bindResource:(NSObject *)control key:(NSString *)pref_key -{ - [self bindResource:(NSObject *)control key:(NSString *)pref_key reload:NO]; -} - - - -- (void) bindSwitch:(NSObject *)control - cmdline:(NSString *)cmd -{ - [self bindResource:control - key:[self switchToResource:cmd opts:opts valRet:0]]; -} - - -#pragma mark Text-manipulating utilities - - -static NSString * -unwrap (NSString *text) -{ - // Unwrap lines: delete \n but do not delete \n\n. - // - NSArray *lines = [text componentsSeparatedByString:@"\n"]; - NSUInteger i, nlines = [lines count]; - BOOL eolp = YES; - - text = @"\n"; // start with one blank line - - // skip trailing blank lines in file - for (i = nlines-1; i > 0; i--) { - NSString *s = (NSString *) [lines objectAtIndex:i]; - if ([s length] > 0) - break; - nlines--; - } - - // skip leading blank lines in file - for (i = 0; i < nlines; i++) { - NSString *s = (NSString *) [lines objectAtIndex:i]; - if ([s length] > 0) - break; - } - - // unwrap - Bool any = NO; - for (; i < nlines; i++) { - NSString *s = (NSString *) [lines objectAtIndex:i]; - if ([s length] == 0) { - text = [text stringByAppendingString:@"\n\n"]; - eolp = YES; - } else if ([s characterAtIndex:0] == ' ' || - [s hasPrefix:@"Copyright "] || - [s hasPrefix:@"https://"] || - [s hasPrefix:@"http://"]) { - // don't unwrap if the following line begins with whitespace, - // or with the word "Copyright", or if it begins with a URL. - if (any && !eolp) - text = [text stringByAppendingString:@"\n"]; - text = [text stringByAppendingString:s]; - any = YES; - eolp = NO; - } else { - if (!eolp) - text = [text stringByAppendingString:@" "]; - text = [text stringByAppendingString:s]; - eolp = NO; - any = YES; - } - } - - return text; -} - - -# ifndef USE_IPHONE -/* Makes the text up to the first comma be bold. - */ -static void -boldify (NSText *nstext) -{ - NSString *text = [nstext string]; - NSRange r = [text rangeOfString:@"," options:0]; - r.length = r.location+1; - - r.location = 0; - - NSFont *font = [nstext font]; - font = [NSFont boldSystemFontOfSize:[font pointSize]]; - [nstext setFont:font range:r]; -} -# endif // !USE_IPHONE - - -/* Creates a human-readable anchor to put on a URL. - */ -static char * -anchorize (const char *url) -{ - const char *wiki1 = "http://en.wikipedia.org/wiki/"; - const char *wiki2 = "https://en.wikipedia.org/wiki/"; - const char *math1 = "http://mathworld.wolfram.com/"; - const char *math2 = "https://mathworld.wolfram.com/"; - if (!strncmp (wiki1, url, strlen(wiki1)) || - !strncmp (wiki2, url, strlen(wiki2))) { - char *anchor = (char *) malloc (strlen(url) * 3 + 10); - strcpy (anchor, "Wikipedia: \""); - const char *in = url + strlen(!strncmp (wiki1, url, strlen(wiki1)) - ? wiki1 : wiki2); - char *out = anchor + strlen(anchor); - while (*in) { - if (*in == '_') { - *out++ = ' '; - } else if (*in == '#') { - *out++ = ':'; - *out++ = ' '; - } else if (*in == '%') { - char hex[3]; - hex[0] = in[1]; - hex[1] = in[2]; - hex[2] = 0; - int n = 0; - sscanf (hex, "%x", &n); - *out++ = (char) n; - in += 2; - } else { - *out++ = *in; - } - in++; - } - *out++ = '"'; - *out = 0; - return anchor; - - } else if (!strncmp (math1, url, strlen(math1)) || - !strncmp (math2, url, strlen(math2))) { - char *anchor = (char *) malloc (strlen(url) * 3 + 10); - strcpy (anchor, "MathWorld: \""); - const char *start = url + strlen(!strncmp (math1, url, strlen(math1)) - ? math1 : math2); - const char *in = start; - char *out = anchor + strlen(anchor); - while (*in) { - if (*in == '_') { - *out++ = ' '; - } else if (in != start && *in >= 'A' && *in <= 'Z') { - *out++ = ' '; - *out++ = *in; - } else if (!strncmp (in, ".htm", 4)) { - break; - } else { - *out++ = *in; - } - in++; - } - *out++ = '"'; - *out = 0; - return anchor; - - } else { - return strdup (url); - } -} - - -#if !defined(USE_IPHONE) || !defined(USE_HTML_LABELS) - -/* Converts any http: URLs in the given text field to clickable links. - */ -static void -hreffify (NSText *nstext) -{ -# ifndef USE_IPHONE - NSString *text = [nstext string]; - [nstext setRichText:YES]; -# else - NSString *text = [nstext text]; -# endif - - NSUInteger L = [text length]; - NSRange start; // range is start-of-search to end-of-string - start.location = 0; - start.length = L; - while (start.location < L) { - - // Find the beginning of a URL... - // - NSRange r2 = [text rangeOfString: @"http://" options:0 range:start]; - NSRange r3 = [text rangeOfString:@"https://" options:0 range:start]; - if ((r2.location == NSNotFound && - r3.location != NSNotFound) || - (r2.location != NSNotFound && - r3.location != NSNotFound && - r3.location < r2.location)) - r2 = r3; - if (r2.location == NSNotFound) - break; - - // Next time around, start searching after this. - start.location = r2.location + r2.length; - start.length = L - start.location; - - // Find the end of a URL (whitespace or EOF)... - // - r3 = [text rangeOfCharacterFromSet: - [NSCharacterSet whitespaceAndNewlineCharacterSet] - options:0 range:start]; - if (r3.location == NSNotFound) // EOF - r3.location = L, r3.length = 0; - - // Next time around, start searching after this. - start.location = r3.location; - start.length = L - start.location; - - // Set r2 to the start/length of this URL. - r2.length = start.location - r2.location; - - // Extract the URL. - NSString *nsurl = [text substringWithRange:r2]; - const char *url = [nsurl UTF8String]; - - // If this is a Wikipedia URL, make the linked text be prettier. - // - char *anchor = anchorize(url); - -# ifndef USE_IPHONE - - // Construct the RTF corresponding to <A HREF="url">anchor</A> - // - const char *fmt = "{\\field{\\*\\fldinst{HYPERLINK \"%s\"}}%s}"; - char *rtf = malloc (strlen (fmt) + strlen(url) + strlen(anchor) + 10); - sprintf (rtf, fmt, url, anchor); - - NSData *rtfdata = [NSData dataWithBytesNoCopy:rtf length:strlen(rtf)]; - [nstext replaceCharactersInRange:r2 withRTF:rtfdata]; - -# else // !USE_IPHONE - // *anchor = 0; // Omit Wikipedia anchor - text = [text stringByReplacingCharactersInRange:r2 - withString:[NSString stringWithCString:anchor - encoding:NSUTF8StringEncoding]]; - // text = [text stringByReplacingOccurrencesOfString:@"\n\n\n" - // withString:@"\n\n"]; -# endif // !USE_IPHONE - - free (anchor); - - NSUInteger L2 = [text length]; // might have changed - start.location -= (L - L2); - L = L2; - } - -# ifdef USE_IPHONE - [nstext setText:text]; - [nstext sizeToFit]; -# endif -} - -#endif /* !USE_IPHONE || !USE_HTML_LABELS */ - - - -#pragma mark Creating controls from XML - - -/* Parse the attributes of an XML tag into a dictionary. - For input, the dictionary should have as attributes the keys, each - with @"" as their value. - On output, the dictionary will set the keys to the values specified, - and keys that were not specified will not be present in the dictionary. - Warnings are printed if there are duplicate or unknown attributes. - */ -- (void) parseAttrs:(NSMutableDictionary *)dict node:(NSXMLNode *)node -{ - NSArray *attrs = [(NSXMLElement *) node attributes]; - NSUInteger n = [attrs count]; - int i; - - // For each key in the dictionary, fill in the dict with the corresponding - // value. The value @"" is assumed to mean "un-set". Issue a warning if - // an attribute is specified twice. - // - for (i = 0; i < n; i++) { - NSXMLNode *attr = [attrs objectAtIndex:i]; - NSString *key = [attr name]; - NSString *val = [attr objectValue]; - NSString *old = [dict objectForKey:key]; - - if (! old) { - NSAssert2 (0, @"unknown attribute \"%@\" in \"%@\"", key, [node name]); - } else if ([old length] != 0) { - NSAssert3 (0, @"duplicate %@: \"%@\", \"%@\"", key, old, val); - } else { - [dict setValue:val forKey:key]; - } - } - - // Remove from the dictionary any keys whose value is still @"", - // meaning there was no such attribute specified. - // - NSArray *keys = [dict allKeys]; - n = [keys count]; - for (i = 0; i < n; i++) { - NSString *key = [keys objectAtIndex:i]; - NSString *val = [dict objectForKey:key]; - if ([val length] == 0) - [dict removeObjectForKey:key]; - } - -# ifdef USE_IPHONE - // Kludge for starwars.xml: - // If there is a "_low-label" and no "_label", but "_low-label" contains - // spaces, divide them. - NSString *lab = [dict objectForKey:@"_label"]; - NSString *low = [dict objectForKey:@"_low-label"]; - if (low && !lab) { - NSArray *split = - [[[low stringByTrimmingCharactersInSet: - [NSCharacterSet whitespaceAndNewlineCharacterSet]] - componentsSeparatedByString: @" "] - filteredArrayUsingPredicate: - [NSPredicate predicateWithFormat:@"length > 0"]]; - if (split && [split count] == 2) { - [dict setValue:[split objectAtIndex:0] forKey:@"_label"]; - [dict setValue:[split objectAtIndex:1] forKey:@"_low-label"]; - } - } -# endif // USE_IPHONE -} - - -/* Handle the options on the top level <xscreensaver> tag. - */ -- (NSString *) parseXScreenSaverTag:(NSXMLNode *)node -{ - NSMutableDictionary *dict = [@{ @"name": @"", - @"_label": @"", - @"gl": @"" } - mutableCopy]; - [self parseAttrs:dict node:node]; - NSString *name = [dict objectForKey:@"name"]; - NSString *label = [dict objectForKey:@"_label"]; - [dict release]; - dict = 0; - - NSAssert1 (label, @"no _label in %@", [node name]); - NSAssert1 (name, @"no name in \"%@\"", label); - return label; -} - - -/* Creates a label: an un-editable NSTextField displaying the given text. - */ -- (LABEL *) makeLabel:(NSString *)text -{ - NSRect rect; - rect.origin.x = rect.origin.y = 0; - rect.size.width = rect.size.height = 10; -# ifndef USE_IPHONE - NSTextField *lab = [[NSTextField alloc] initWithFrame:rect]; - [lab setSelectable:NO]; - [lab setEditable:NO]; - [lab setBezeled:NO]; - [lab setDrawsBackground:NO]; - [lab setStringValue:text]; - [lab sizeToFit]; -# else // USE_IPHONE - UILabel *lab = [[UILabel alloc] initWithFrame:rect]; - [lab setText: [text stringByTrimmingCharactersInSet: - [NSCharacterSet whitespaceAndNewlineCharacterSet]]]; - [lab setBackgroundColor:[UIColor clearColor]]; - [lab setNumberOfLines:0]; // unlimited - // [lab setLineBreakMode:UILineBreakModeWordWrap]; - [lab setLineBreakMode:NSLineBreakByTruncatingHead]; - [lab setAutoresizingMask: (UIViewAutoresizingFlexibleWidth | - UIViewAutoresizingFlexibleHeight)]; -# endif // USE_IPHONE - [lab autorelease]; - return lab; -} - - -/* Creates the checkbox (NSButton) described by the given XML node. - */ -- (void) makeCheckbox:(NSXMLNode *)node on:(NSView *)parent -{ - NSMutableDictionary *dict = [@{ @"id": @"", - @"_label": @"", - @"arg-set": @"", - @"arg-unset": @"" } - mutableCopy]; - [self parseAttrs:dict node:node]; - NSString *label = [dict objectForKey:@"_label"]; - NSString *arg_set = [dict objectForKey:@"arg-set"]; - NSString *arg_unset = [dict objectForKey:@"arg-unset"]; - [dict release]; - dict = 0; - - if (!label) { - NSAssert1 (0, @"no _label in %@", [node name]); - return; - } - if (!arg_set && !arg_unset) { - NSAssert1 (0, @"neither arg-set nor arg-unset provided in \"%@\"", - label); - } - if (arg_set && arg_unset) { - NSAssert1 (0, @"only one of arg-set and arg-unset may be used in \"%@\"", - label); - } - - // sanity-check the choice of argument names. - // - if (arg_set && ([arg_set hasPrefix:@"-no-"] || - [arg_set hasPrefix:@"--no-"])) - NSLog (@"arg-set should not be a \"no\" option in \"%@\": %@", - label, arg_set); - if (arg_unset && (![arg_unset hasPrefix:@"-no-"] && - ![arg_unset hasPrefix:@"--no-"])) - NSLog(@"arg-unset should be a \"no\" option in \"%@\": %@", - label, arg_unset); - - NSRect rect; - rect.origin.x = rect.origin.y = 0; - rect.size.width = rect.size.height = 10; - -# ifndef USE_IPHONE - - NSButton *button = [[NSButton alloc] initWithFrame:rect]; - [button setButtonType:NSSwitchButton]; - [button setTitle:label]; - [button sizeToFit]; - [self placeChild:button on:parent]; - -# else // USE_IPHONE - - LABEL *lab = [self makeLabel:label]; - [self placeChild:lab on:parent]; - UISwitch *button = [[UISwitch alloc] initWithFrame:rect]; - [self placeChild:button on:parent right:YES]; - -# endif // USE_IPHONE - - [self bindSwitch:button cmdline:(arg_set ? arg_set : arg_unset)]; - [button release]; -} - - -/* Creates the number selection control described by the given XML node. - If "type=slider", it's an NSSlider. - If "type=spinbutton", it's a text field with up/down arrows next to it. - */ -- (void) makeNumberSelector:(NSXMLNode *)node on:(NSView *)parent -{ - NSMutableDictionary *dict = [@{ @"id": @"", - @"_label": @"", - @"_low-label": @"", - @"_high-label": @"", - @"type": @"", - @"arg": @"", - @"low": @"", - @"high": @"", - @"default": @"", - @"convert": @"" } - mutableCopy]; - [self parseAttrs:dict node:node]; - NSString *label = [dict objectForKey:@"_label"]; - NSString *low_label = [dict objectForKey:@"_low-label"]; - NSString *high_label = [dict objectForKey:@"_high-label"]; - NSString *type = [dict objectForKey:@"type"]; - NSString *arg = [dict objectForKey:@"arg"]; - NSString *low = [dict objectForKey:@"low"]; - NSString *high = [dict objectForKey:@"high"]; - NSString *def = [dict objectForKey:@"default"]; - NSString *cvt = [dict objectForKey:@"convert"]; - [dict release]; - dict = 0; - - NSAssert1 (arg, @"no arg in %@", label); - NSAssert1 (type, @"no type in %@", label); - - if (! low) { - NSAssert1 (0, @"no low in %@", [node name]); - return; - } - if (! high) { - NSAssert1 (0, @"no high in %@", [node name]); - return; - } - if (! def) { - NSAssert1 (0, @"no default in %@", [node name]); - return; - } - if (cvt && ![cvt isEqualToString:@"invert"]) { - NSAssert1 (0, @"if provided, \"convert\" must be \"invert\" in %@", - label); - } - - // If either the min or max field contains a decimal point, then this - // option may have a floating point value; otherwise, it is constrained - // to be an integer. - // - NSCharacterSet *dot = - [NSCharacterSet characterSetWithCharactersInString:@"."]; - BOOL float_p = ([low rangeOfCharacterFromSet:dot].location != NSNotFound || - [high rangeOfCharacterFromSet:dot].location != NSNotFound); - - if ([type isEqualToString:@"slider"] -# ifdef USE_IPHONE // On iPhone, we use sliders for all numeric values. - || [type isEqualToString:@"spinbutton"] -# endif - ) { - - NSRect rect; - rect.origin.x = rect.origin.y = 0; - rect.size.width = 150; - rect.size.height = 23; // apparent min height for slider with ticks... - NSSlider *slider; - slider = [[InvertedSlider alloc] initWithFrame:rect - inverted: !!cvt - integers: !float_p]; - [slider setMaxValue:[high doubleValue]]; - [slider setMinValue:[low doubleValue]]; - - int range = [slider maxValue] - [slider minValue] + 1; - int range2 = range; - int max_ticks = 21; - while (range2 > max_ticks) - range2 /= 10; - -# ifndef USE_IPHONE - // If we have elided ticks, leave it at the max number of ticks. - if (range != range2 && range2 < max_ticks) - range2 = max_ticks; - - // If it's a float, always display the max number of ticks. - if (float_p && range2 < max_ticks) - range2 = max_ticks; - - [slider setNumberOfTickMarks:range2]; - - [slider setAllowsTickMarkValuesOnly: - (range == range2 && // we are showing the actual number of ticks - !float_p)]; // and we want integer results -# endif // !USE_IPHONE - - // #### Note: when the slider's range is large enough that we aren't - // showing all possible ticks, the slider's value is not constrained - // to be an integer, even though it should be... - // Maybe we need to use a value converter or something? - - LABEL *lab; - if (label) { - lab = [self makeLabel:label]; - [self placeChild:lab on:parent]; -# ifdef USE_IPHONE - if (low_label) { - CGFloat s = [NSFont systemFontSize] + 4; - [lab setFont:[NSFont boldSystemFontOfSize:s]]; - } -# endif - } - - if (low_label) { - lab = [self makeLabel:low_label]; - [lab setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; -# ifndef USE_IPHONE - [lab setAlignment:1]; // right aligned - rect = [lab frame]; - if (rect.size.width < LEFT_LABEL_WIDTH) - rect.size.width = LEFT_LABEL_WIDTH; // make all left labels same size - rect.size.height = [slider frame].size.height; - [lab setFrame:rect]; - [self placeChild:lab on:parent]; -# else // USE_IPHONE - [lab setTextAlignment: NSTextAlignmentRight]; - // Sometimes rotation screws up truncation. - [lab setLineBreakMode:NSLineBreakByClipping]; - [self placeChild:lab on:parent right:(label ? YES : NO)]; -# endif // USE_IPHONE - } - -# ifndef USE_IPHONE - [self placeChild:slider on:parent right:(low_label ? YES : NO)]; -# else // USE_IPHONE - [self placeChild:slider on:parent right:(label || low_label ? YES : NO)]; -# endif // USE_IPHONE - - if (low_label) { - // Make left label be same height as slider. - rect = [lab frame]; - rect.size.height = [slider frame].size.height; - [lab setFrame:rect]; - } - - if (! low_label) { - rect = [slider frame]; - if (rect.origin.x < LEFT_LABEL_WIDTH) - rect.origin.x = LEFT_LABEL_WIDTH; // make unlabelled sliders line up too - [slider setFrame:rect]; - } - - if (high_label) { - lab = [self makeLabel:high_label]; - [lab setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; - rect = [lab frame]; - - // Make right label be same height as slider. - rect.size.height = [slider frame].size.height; - [lab setFrame:rect]; -# ifdef USE_IPHONE - // Sometimes rotation screws up truncation. - [lab setLineBreakMode:NSLineBreakByClipping]; -# endif - [self placeChild:lab on:parent right:YES]; - } - - [self bindSwitch:slider cmdline:arg]; - [slider release]; - -#ifndef USE_IPHONE // On iPhone, we use sliders for all numeric values. - - } else if ([type isEqualToString:@"spinbutton"]) { - - if (! label) { - NSAssert1 (0, @"no _label in spinbutton %@", [node name]); - return; - } - NSAssert1 (!low_label, - @"low-label not allowed in spinbutton \"%@\"", [node name]); - NSAssert1 (!high_label, - @"high-label not allowed in spinbutton \"%@\"", [node name]); - NSAssert1 (!cvt, @"convert not allowed in spinbutton \"%@\"", - [node name]); - - NSRect rect; - rect.origin.x = rect.origin.y = 0; - rect.size.width = rect.size.height = 10; - - NSTextField *txt = [[NSTextField alloc] initWithFrame:rect]; - [txt setStringValue:@"0000.0"]; - [txt sizeToFit]; - [txt setStringValue:@""]; - - if (label) { - LABEL *lab = [self makeLabel:label]; - //[lab setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; - [lab setAlignment:1]; // right aligned - rect = [lab frame]; - if (rect.size.width < LEFT_LABEL_WIDTH) - rect.size.width = LEFT_LABEL_WIDTH; // make all left labels same size - rect.size.height = [txt frame].size.height; - [lab setFrame:rect]; - [self placeChild:lab on:parent]; - } - - [self placeChild:txt on:parent right:(label ? YES : NO)]; - - if (! label) { - rect = [txt frame]; - if (rect.origin.x < LEFT_LABEL_WIDTH) - rect.origin.x = LEFT_LABEL_WIDTH; // make unlabelled spinbtns line up - [txt setFrame:rect]; - } - - rect.size.width = rect.size.height = 10; - NSStepper *step = [[NSStepper alloc] initWithFrame:rect]; - [step sizeToFit]; - [self placeChild:step on:parent right:YES]; - rect = [step frame]; - rect.origin.x -= COLUMN_SPACING; // this one goes close - rect.origin.y += ([txt frame].size.height - rect.size.height) / 2; - [step setFrame:rect]; - - [step setMinValue:[low doubleValue]]; - [step setMaxValue:[high doubleValue]]; - [step setAutorepeat:YES]; - [step setValueWraps:NO]; - - double range = [high doubleValue] - [low doubleValue]; - if (range < 1.0) - [step setIncrement:range / 10.0]; - else if (range >= 500) - [step setIncrement:range / 100.0]; - else - [step setIncrement:1.0]; - - NSNumberFormatter *fmt = [[[NSNumberFormatter alloc] init] autorelease]; - [fmt setFormatterBehavior:NSNumberFormatterBehavior10_4]; - [fmt setNumberStyle:NSNumberFormatterDecimalStyle]; - [fmt setMinimum:[NSNumber numberWithDouble:[low doubleValue]]]; - [fmt setMaximum:[NSNumber numberWithDouble:[high doubleValue]]]; - [fmt setMinimumFractionDigits: (float_p ? 1 : 0)]; - [fmt setMaximumFractionDigits: (float_p ? 2 : 0)]; - - [fmt setGeneratesDecimalNumbers:float_p]; - [[txt cell] setFormatter:fmt]; - - [self bindSwitch:step cmdline:arg]; - [self bindSwitch:txt cmdline:arg]; - - [step release]; - [txt release]; - -# endif // USE_IPHONE - - } else { - NSAssert2 (0, @"unknown type \"%@\" in \"%@\"", type, label); - } -} - - -# ifndef USE_IPHONE -static void -set_menu_item_object (NSMenuItem *item, NSObject *obj) -{ - /* If the object associated with this menu item looks like a boolean, - store an NSNumber instead of an NSString, since that's what - will be in the preferences (due to similar logic in PrefsReader). - */ - if ([obj isKindOfClass:[NSString class]]) { - NSString *string = (NSString *) obj; - if (NSOrderedSame == [string caseInsensitiveCompare:@"true"] || - NSOrderedSame == [string caseInsensitiveCompare:@"yes"]) - obj = [NSNumber numberWithBool:YES]; - else if (NSOrderedSame == [string caseInsensitiveCompare:@"false"] || - NSOrderedSame == [string caseInsensitiveCompare:@"no"]) - obj = [NSNumber numberWithBool:NO]; - else - obj = string; - } - - [item setRepresentedObject:obj]; - //NSLog (@"menu item \"%@\" = \"%@\" %@", [item title], obj, [obj class]); -} -# endif // !USE_IPHONE - - -/* Creates the popup menu described by the given XML node (and its children). - */ -- (void) makeOptionMenu:(NSXMLNode *)node on:(NSView *)parent -{ - NSArray *children = [node children]; - NSUInteger i, count = [children count]; - - if (count <= 0) { - NSAssert1 (0, @"no menu items in \"%@\"", [node name]); - return; - } - - // get the "id" attribute off the <select> tag. - // - NSMutableDictionary *dict = [@{ @"id": @"", } mutableCopy]; - [self parseAttrs:dict node:node]; - [dict release]; - dict = 0; - - NSRect rect; - rect.origin.x = rect.origin.y = 0; - rect.size.width = 10; - rect.size.height = 10; - - NSString *menu_key = nil; // the resource key used by items in this menu - -# ifndef USE_IPHONE - // #### "Build and Analyze" says that all of our widgets leak, because it - // seems to not realize that placeChild -> addSubview retains them. - // Not sure what to do to make these warnings go away. - - NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:rect - pullsDown:NO]; - NSMenuItem *def_item = nil; - float max_width = 0; - -# else // USE_IPHONE - - NSString *def_item = nil; - - rect.size.width = 0; - rect.size.height = 0; -# ifdef USE_PICKER_VIEW - UIPickerView *popup = [[[UIPickerView alloc] initWithFrame:rect] retain]; - popup.delegate = self; - popup.dataSource = self; -# endif // !USE_PICKER_VIEW - NSMutableArray *items = [NSMutableArray arrayWithCapacity:10]; - -# endif // USE_IPHONE - - for (i = 0; i < count; i++) { - NSXMLNode *child = [children objectAtIndex:i]; - - if ([child kind] == NSXMLCommentKind) - continue; - if ([child kind] != NSXMLElementKind) { -// NSAssert2 (0, @"weird XML node kind: %d: %@", (int)[child kind], node); - continue; - } - - // get the "id", "_label", and "arg-set" attrs off of the <option> tags. - // - NSMutableDictionary *dict2 = [@{ @"id": @"", - @"_label": @"", - @"arg-set": @"" } - mutableCopy]; - [self parseAttrs:dict2 node:child]; - NSString *label = [dict2 objectForKey:@"_label"]; - NSString *arg_set = [dict2 objectForKey:@"arg-set"]; - [dict2 release]; - dict2 = 0; - - if (!label) { - NSAssert1 (0, @"no _label in %@", [child name]); - continue; - } - -# ifndef USE_IPHONE - // create the menu item (and then get a pointer to it) - [popup addItemWithTitle:label]; - NSMenuItem *item = [popup itemWithTitle:label]; -# endif // USE_IPHONE - - if (arg_set) { - NSString *this_val = NULL; - NSString *this_key = [self switchToResource: arg_set - opts: opts - valRet: &this_val]; - NSAssert1 (this_val, @"this_val null for %@", arg_set); - if (menu_key && ![menu_key isEqualToString:this_key]) - NSAssert3 (0, - @"multiple resources in menu: \"%@\" vs \"%@\" = \"%@\"", - menu_key, this_key, this_val); - if (this_key) - menu_key = this_key; - - /* If this menu has the cmd line "-mode foo" then set this item's - value to "foo" (the menu itself will be bound to e.g. "modeString") - */ -# ifndef USE_IPHONE - set_menu_item_object (item, this_val); -# else - // Array holds ["Label", "resource-key", "resource-val"]. - [items addObject:[NSMutableArray arrayWithObjects: - label, @"", this_val, nil]]; -# endif - - } else { - // no arg-set -- only one menu item can be missing that. - NSAssert1 (!def_item, @"no arg-set in \"%@\"", label); -# ifndef USE_IPHONE - def_item = item; -# else - def_item = label; - // Array holds ["Label", "resource-key", "resource-val"]. - [items addObject:[NSMutableArray arrayWithObjects: - label, @"", @"", nil]]; -# endif - } - - /* make sure the menu button has room for the text of this item, - and remember the greatest width it has reached. - */ -# ifndef USE_IPHONE - [popup setTitle:label]; - [popup sizeToFit]; - NSRect r = [popup frame]; - if (r.size.width > max_width) max_width = r.size.width; -# endif // USE_IPHONE - } - - if (!menu_key) { - NSAssert1 (0, @"no switches in menu \"%@\"", [dict objectForKey:@"id"]); - return; - } - - /* We've added all of the menu items. If there was an item with no - command-line switch, then it's the item that represents the default - value. Now we must bind to that item as well... (We have to bind - this one late, because if it was the first item, then we didn't - yet know what resource was associated with this menu.) - */ - if (def_item) { - NSObject *def_obj = [defaultOptions objectForKey:menu_key]; - NSAssert2 (def_obj, - @"no default value for resource \"%@\" in menu item \"%@\"", - menu_key, -# ifndef USE_IPHONE - [def_item title] -# else - def_item -# endif - ); - -# ifndef USE_IPHONE - set_menu_item_object (def_item, def_obj); -# else // !USE_IPHONE - for (NSMutableArray *a in items) { - // Make sure each array contains the resource key. - [a replaceObjectAtIndex:1 withObject:menu_key]; - // Make sure the default item contains the default resource value. - if (def_obj && def_item && - [def_item isEqualToString:[a objectAtIndex:0]]) - [a replaceObjectAtIndex:2 withObject:def_obj]; - } -# endif // !USE_IPHONE - } - -# ifndef USE_IPHONE -# ifdef USE_PICKER_VIEW - /* Finish tweaking the menu button itself. - */ - if (def_item) - [popup setTitle:[def_item title]]; - NSRect r = [popup frame]; - r.size.width = max_width; - [popup setFrame:r]; -# endif // USE_PICKER_VIEW -# endif - -# if !defined(USE_IPHONE) || defined(USE_PICKER_VIEW) - [self placeChild:popup on:parent]; - [self bindResource:popup key:menu_key]; - [popup release]; -# endif - -# ifdef USE_IPHONE -# ifdef USE_PICKER_VIEW - // Store the items for this picker in the picker_values array. - // This is so fucking stupid. - - unsigned long menu_number = [pref_keys count] - 1; - if (! picker_values) - picker_values = [[NSMutableArray arrayWithCapacity:menu_number] retain]; - while ([picker_values count] <= menu_number) - [picker_values addObject:[NSArray arrayWithObjects: nil]]; - [picker_values replaceObjectAtIndex:menu_number withObject:items]; - [popup reloadAllComponents]; - -# else // !USE_PICKER_VIEW - - [self placeSeparator]; - - i = 0; - for (__attribute__((unused)) NSArray *item in items) { - RadioButton *b = [[RadioButton alloc] initWithIndex: (int)i - items:items]; - [b setLineBreakMode:NSLineBreakByTruncatingHead]; - [b setFont:[NSFont boldSystemFontOfSize: FONT_SIZE]]; - [self placeChild:b on:parent]; - [b release]; - i++; - } - - [self placeSeparator]; - -# endif // !USE_PICKER_VIEW -# endif // !USE_IPHONE - -} - - -/* Creates an uneditable, wrapping NSTextField to display the given - text enclosed by <description> ... </description> in the XML. - */ -- (void) makeDescLabel:(NSXMLNode *)node on:(NSView *)parent -{ - NSString *text = nil; - NSArray *children = [node children]; - NSUInteger i, count = [children count]; - - for (i = 0; i < count; i++) { - NSXMLNode *child = [children objectAtIndex:i]; - NSString *s = [child objectValue]; - if (text) - text = [text stringByAppendingString:s]; - else - text = s; - } - - text = unwrap (text); - - NSRect rect = [parent frame]; - rect.origin.x = rect.origin.y = 0; - rect.size.width = 200; - rect.size.height = 50; // sized later -# ifndef USE_IPHONE - NSText *lab = [[NSText alloc] initWithFrame:rect]; - [lab autorelease]; - [lab setEditable:NO]; - [lab setDrawsBackground:NO]; - [lab setHorizontallyResizable:YES]; - [lab setVerticallyResizable:YES]; - [lab setString:text]; - hreffify (lab); - boldify (lab); - [lab sizeToFit]; - -# else // USE_IPHONE - -# ifndef USE_HTML_LABELS - - UILabel *lab = [self makeLabel:text]; - [lab setFont:[NSFont systemFontOfSize: [NSFont systemFontSize]]]; - hreffify (lab); - -# else // USE_HTML_LABELS - HTMLLabel *lab = [[HTMLLabel alloc] - initWithText:text - font:[NSFont systemFontOfSize: [NSFont systemFontSize]]]; - [lab autorelease]; - [lab setFrame:rect]; - [lab sizeToFit]; -# endif // USE_HTML_LABELS - - [self placeSeparator]; - -# endif // USE_IPHONE - - [self placeChild:lab on:parent]; -} - - -/* Creates the NSTextField described by the given XML node. - */ -- (void) makeTextField: (NSXMLNode *)node - on: (NSView *)parent - withLabel: (BOOL) label_p - horizontal: (BOOL) horiz_p -{ - NSMutableDictionary *dict = [@{ @"id": @"", - @"_label": @"", - @"arg": @"" } - mutableCopy]; - [self parseAttrs:dict node:node]; - NSString *label = [dict objectForKey:@"_label"]; - NSString *arg = [dict objectForKey:@"arg"]; - [dict release]; - dict = 0; - - if (!label && label_p) { - NSAssert1 (0, @"no _label in %@", [node name]); - return; - } - - NSAssert1 (arg, @"no arg in %@", label); - - NSRect rect; - rect.origin.x = rect.origin.y = 0; - rect.size.width = rect.size.height = 10; - - NSTextField *txt = [[NSTextField alloc] initWithFrame:rect]; - -# ifndef USE_IPHONE - - // make the default size be around 30 columns; a typical value for - // these text fields is "xscreensaver-text --cols 40". - // - [txt setStringValue:@"123456789 123456789 123456789 "]; - [txt sizeToFit]; - [[txt cell] setWraps:NO]; - [[txt cell] setScrollable:YES]; - [txt setStringValue:@""]; - -# else // USE_IPHONE - - txt.adjustsFontSizeToFitWidth = YES; - txt.textColor = [UIColor blackColor]; - txt.font = [UIFont systemFontOfSize: FONT_SIZE]; - txt.placeholder = @""; - txt.borderStyle = UITextBorderStyleRoundedRect; - txt.textAlignment = NSTextAlignmentRight; - txt.keyboardType = UIKeyboardTypeDefault; // Full kbd - txt.autocorrectionType = UITextAutocorrectionTypeNo; - txt.autocapitalizationType = UITextAutocapitalizationTypeNone; - txt.clearButtonMode = UITextFieldViewModeAlways; - txt.returnKeyType = UIReturnKeyDone; - txt.delegate = self; - txt.text = @""; - [txt setEnabled: YES]; - - rect.size.height = [txt.font lineHeight] * 1.2; - [txt setFrame:rect]; - -# endif // USE_IPHONE - - if (label) { - LABEL *lab = [self makeLabel:label]; - [self placeChild:lab on:parent]; - } - - [self placeChild:txt on:parent right:(label ? YES : NO)]; - - [self bindSwitch:txt cmdline:arg]; - [txt release]; -} - - -/* Creates the NSTextField described by the given XML node, - and hooks it up to a Choose button and a file selector widget. - */ -- (void) makeFileSelector: (NSXMLNode *)node - on: (NSView *)parent - dirsOnly: (BOOL) dirsOnly - withLabel: (BOOL) label_p - editable: (BOOL) editable_p -{ -# ifndef USE_IPHONE // No files. No selectors. - NSMutableDictionary *dict = [@{ @"id": @"", - @"_label": @"", - @"arg": @"" } - mutableCopy]; - [self parseAttrs:dict node:node]; - NSString *label = [dict objectForKey:@"_label"]; - NSString *arg = [dict objectForKey:@"arg"]; - [dict release]; - dict = 0; - - if (!label && label_p) { - NSAssert1 (0, @"no _label in %@", [node name]); - return; - } - - NSAssert1 (arg, @"no arg in %@", label); - - NSRect rect; - rect.origin.x = rect.origin.y = 0; - rect.size.width = rect.size.height = 10; - - NSTextField *txt = [[NSTextField alloc] initWithFrame:rect]; - - // make the default size be around 20 columns. - // - [txt setStringValue:@"123456789 123456789 "]; - [txt sizeToFit]; - [txt setSelectable:YES]; - [txt setEditable:editable_p]; - [txt setBezeled:editable_p]; - [txt setDrawsBackground:editable_p]; - [[txt cell] setWraps:NO]; - [[txt cell] setScrollable:YES]; - [[txt cell] setLineBreakMode:NSLineBreakByTruncatingHead]; - [txt setStringValue:@""]; - - LABEL *lab = 0; - if (label) { - lab = [self makeLabel:label]; - [self placeChild:lab on:parent]; - } - - [self placeChild:txt on:parent right:(label ? YES : NO)]; - - [self bindSwitch:txt cmdline:arg]; - [txt release]; - - // Make the text field and label be the same height, whichever is taller. - if (lab) { - rect = [txt frame]; - rect.size.height = ([lab frame].size.height > [txt frame].size.height - ? [lab frame].size.height - : [txt frame].size.height); - [txt setFrame:rect]; - } - - // Now put a "Choose" button next to it. - // - rect.origin.x = rect.origin.y = 0; - rect.size.width = rect.size.height = 10; - NSButton *choose = [[NSButton alloc] initWithFrame:rect]; - [choose setTitle:@"Choose..."]; - [choose setBezelStyle:NSRoundedBezelStyle]; - [choose sizeToFit]; - - [self placeChild:choose on:parent right:YES]; - - // center the Choose button around the midpoint of the text field. - rect = [choose frame]; - rect.origin.y = ([txt frame].origin.y + - (([txt frame].size.height - rect.size.height) / 2)); - [choose setFrameOrigin:rect.origin]; - - [choose setTarget:[parent window]]; - if (dirsOnly) - [choose setAction:@selector(fileSelectorChooseDirsAction:)]; - else - [choose setAction:@selector(fileSelectorChooseAction:)]; - - [choose release]; -# endif // !USE_IPHONE -} - - -# ifndef USE_IPHONE - -/* Runs a modal file selector and sets the text field's value to the - selected file or directory. - */ -static void -do_file_selector (NSTextField *txt, BOOL dirs_p) -{ - NSOpenPanel *panel = [NSOpenPanel openPanel]; - [panel setAllowsMultipleSelection:NO]; - [panel setCanChooseFiles:!dirs_p]; - [panel setCanChooseDirectories:dirs_p]; - - NSInteger result = [panel runModal]; - if (result == NSOKButton) { - NSArray *files = [panel URLs]; - NSString *file = ([files count] > 0 ? [[files objectAtIndex:0] path] : @""); - file = [file stringByAbbreviatingWithTildeInPath]; - [txt setStringValue:file]; - - // Fuck me! Just setting the value of the NSTextField does not cause - // that to end up in the preferences! - // - NSDictionary *dict = [txt infoForBinding:@"value"]; - NSUserDefaultsController *prefs = [dict objectForKey:@"NSObservedObject"]; - NSString *path = [dict objectForKey:@"NSObservedKeyPath"]; - if ([path hasPrefix:@"values."]) // WTF. - path = [path substringFromIndex:7]; - [[prefs values] setValue:file forKey:path]; - } -} - - -/* Returns the NSTextField that is to the left of or above the NSButton. - */ -static NSTextField * -find_text_field_of_button (NSButton *button) -{ - NSView *parent = [button superview]; - NSArray *kids = [parent subviews]; - NSUInteger nkids = [kids count]; - int i; - NSTextField *f = 0; - for (i = 0; i < nkids; i++) { - NSObject *kid = [kids objectAtIndex:i]; - if ([kid isKindOfClass:[NSTextField class]]) { - f = (NSTextField *) kid; - } else if (kid == button) { - if (! f) abort(); - return f; - } - } - abort(); -} - - -- (void) fileSelectorChooseAction:(NSObject *)arg -{ - NSButton *choose = (NSButton *) arg; - NSTextField *txt = find_text_field_of_button (choose); - do_file_selector (txt, NO); -} - -- (void) fileSelectorChooseDirsAction:(NSObject *)arg -{ - NSButton *choose = (NSButton *) arg; - NSTextField *txt = find_text_field_of_button (choose); - do_file_selector (txt, YES); -} - -#endif // !USE_IPHONE - - -- (void) makeTextLoaderControlBox:(NSXMLNode *)node on:(NSView *)parent -{ -# ifndef USE_IPHONE - /* - Display Text: - (x) Computer name and time - ( ) Text [__________________________] - ( ) Text file [_________________] [Choose] - ( ) URL [__________________________] - ( ) Shell Cmd [__________________________] - - textMode -text-mode date - textMode -text-mode literal textLiteral -text-literal % - textMode -text-mode file textFile -text-file % - textMode -text-mode url textURL -text-url % - textMode -text-mode program textProgram -text-program % - */ - NSRect rect; - rect.size.width = rect.size.height = 1; - rect.origin.x = rect.origin.y = 0; - NSView *group = [[NSView alloc] initWithFrame:rect]; - NSView *rgroup = [[NSView alloc] initWithFrame:rect]; - - Bool program_p = TRUE; - - - NSView *control; - - // This is how you link radio buttons together. - // - NSButtonCell *proto = [[NSButtonCell alloc] init]; - [proto setButtonType:NSRadioButton]; - - rect.origin.x = rect.origin.y = 0; - rect.size.width = rect.size.height = 10; - NSMatrix *matrix = [[NSMatrix alloc] - initWithFrame:rect - mode:NSRadioModeMatrix - prototype:proto - numberOfRows: 4 + (program_p ? 1 : 0) - numberOfColumns:1]; - [matrix setAllowsEmptySelection:NO]; - - NSArrayController *cnames = [[NSArrayController alloc] initWithContent:nil]; - [cnames addObject:@"Computer name and time"]; - [cnames addObject:@"Text"]; - [cnames addObject:@"File"]; - [cnames addObject:@"URL"]; - if (program_p) [cnames addObject:@"Shell Cmd"]; - [matrix bind:@"content" - toObject:cnames - withKeyPath:@"arrangedObjects" - options:nil]; - [cnames release]; - - [self bindSwitch:matrix cmdline:@"-text-mode %"]; - - [self placeChild:matrix on:group]; - [self placeChild:rgroup on:group right:YES]; - [proto release]; - [matrix release]; - [rgroup release]; - - NSXMLNode *node2; - -# else // USE_IPHONE - - NSView *rgroup = parent; - NSXMLNode *node2; - - // <select id="textMode"> - // <option id="date" _label="Display date" arg-set="-text-mode date"/> - // <option id="text" _label="Display text" arg-set="-text-mode literal"/> - // <option id="url" _label="Display URL"/> - // </select> - - node2 = [[NSXMLElement alloc] initWithName:@"select"]; - [node2 setAttributesAsDictionary:@{ @"id": @"textMode" }]; - - NSXMLNode *node3 = [[NSXMLElement alloc] initWithName:@"option"]; - [node3 setAttributesAsDictionary: - @{ @"id": @"date", - @"arg-set": @"-text-mode date", - @"_label": @"Display the date and time" }]; - [node3 setParent: node2]; - [node3 autorelease]; - - node3 = [[NSXMLElement alloc] initWithName:@"option"]; - [node3 setAttributesAsDictionary: - @{ @"id": @"text", - @"arg-set": @"-text-mode literal", - @"_label": @"Display static text" }]; - [node3 setParent: node2]; - [node3 autorelease]; - - node3 = [[NSXMLElement alloc] initWithName:@"option"]; - [node3 setAttributesAsDictionary: - @{ @"id": @"url", - @"_label": @"Display the contents of a URL" }]; - [node3 setParent: node2]; - [node3 autorelease]; - - [self makeOptionMenu:node2 on:rgroup]; - [node2 release]; - -# endif // USE_IPHONE - - - // <string id="textLiteral" _label="" arg-set="-text-literal %"/> - node2 = [[NSXMLElement alloc] initWithName:@"string"]; - [node2 setAttributesAsDictionary: - @{ @"id": @"textLiteral", - @"arg": @"-text-literal %", -# ifdef USE_IPHONE - @"_label": @"Text to display" -# endif - }]; - [self makeTextField:node2 on:rgroup -# ifndef USE_IPHONE - withLabel:NO -# else - withLabel:YES -# endif - horizontal:NO]; - [node2 release]; - -// rect = [last_child(rgroup) frame]; - -/* // trying to make the text fields be enabled only when the checkbox is on.. - control = last_child (rgroup); - [control bind:@"enabled" - toObject:[matrix cellAtRow:1 column:0] - withKeyPath:@"value" - options:nil]; - */ - - -# ifndef USE_IPHONE - // <file id="textFile" _label="" arg-set="-text-file %"/> - node2 = [[NSXMLElement alloc] initWithName:@"string"]; - [node2 setAttributesAsDictionary: - @{ @"id": @"textFile", - @"arg": @"-text-file %" }]; - [self makeFileSelector:node2 on:rgroup - dirsOnly:NO withLabel:NO editable:NO]; - [node2 release]; -# endif // !USE_IPHONE - -// rect = [last_child(rgroup) frame]; - - // <string id="textURL" _label="" arg-set="text-url %"/> - node2 = [[NSXMLElement alloc] initWithName:@"string"]; - [node2 setAttributesAsDictionary: - @{ @"id": @"textURL", - @"arg": @"-text-url %", -# ifdef USE_IPHONE - @"_label": @"URL to display", -# endif - }]; - [self makeTextField:node2 on:rgroup -# ifndef USE_IPHONE - withLabel:NO -# else - withLabel:YES -# endif - horizontal:NO]; - [node2 release]; - -// rect = [last_child(rgroup) frame]; - -# ifndef USE_IPHONE - if (program_p) { - // <string id="textProgram" _label="" arg-set="text-program %"/> - node2 = [[NSXMLElement alloc] initWithName:@"string"]; - [node2 setAttributesAsDictionary: - @{ @"id": @"textProgram", - @"arg": @"-text-program %", - }]; - [self makeTextField:node2 on:rgroup withLabel:NO horizontal:NO]; - [node2 release]; - } - -// rect = [last_child(rgroup) frame]; - - layout_group (rgroup, NO); - - rect = [rgroup frame]; - rect.size.width += 35; // WTF? Why is rgroup too narrow? - [rgroup setFrame:rect]; - - - // Set the height of the cells in the radio-box matrix to the height of - // the (last of the) text fields. - control = last_child (rgroup); - rect = [control frame]; - rect.size.width = 30; // width of the string "Text", plus a bit... - if (program_p) - rect.size.width += 25; - rect.size.height += LINE_SPACING; - [matrix setCellSize:rect.size]; - [matrix sizeToCells]; - - layout_group (group, YES); - rect = [matrix frame]; - rect.origin.x += rect.size.width + COLUMN_SPACING; - rect.origin.y -= [control frame].size.height - LINE_SPACING; - [rgroup setFrameOrigin:rect.origin]; - - // now cheat on the size of the matrix: allow it to overlap (underlap) - // the text fields. - // - rect.size = [matrix cellSize]; - rect.size.width = 300; - [matrix setCellSize:rect.size]; - [matrix sizeToCells]; - - // Cheat on the position of the stuff on the right (the rgroup). - // GAAAH, this code is such crap! - rect = [rgroup frame]; - rect.origin.y -= 5; - [rgroup setFrame:rect]; - - - rect.size.width = rect.size.height = 0; - NSBox *box = [[NSBox alloc] initWithFrame:rect]; - [box setTitlePosition:NSAtTop]; - [box setBorderType:NSBezelBorder]; - [box setTitle:@"Display Text"]; - - rect.size.width = rect.size.height = 12; - [box setContentViewMargins:rect.size]; - [box setContentView:group]; - [box sizeToFit]; - - [self placeChild:box on:parent]; - [group release]; - [box release]; - -# endif // !USE_IPHONE -} - - -- (void) makeImageLoaderControlBox:(NSXMLNode *)node on:(NSView *)parent -{ - /* - [x] Grab desktop images - [ ] Choose random image: - [__________________________] [Choose] - - <boolean id="grabDesktopImages" _label="Grab desktop images" - arg-unset="-no-grab-desktop"/> - <boolean id="chooseRandomImages" _label="Grab desktop images" - arg-unset="-choose-random-images"/> - <file id="imageDirectory" _label="" arg-set="-image-directory %"/> - */ - - NSXMLElement *node2; - -# ifndef USE_IPHONE -# define SCREENS "Grab desktop images" -# define PHOTOS "Choose random images" -# else -# define SCREENS "Grab screenshots" -# define PHOTOS "Use photo library" -# endif - - node2 = [[NSXMLElement alloc] initWithName:@"boolean"]; - [node2 setAttributesAsDictionary: - @{ @"id": @"grabDesktopImages", - @"_label": @ SCREENS, - @"arg-unset": @"-no-grab-desktop", - }]; - [self makeCheckbox:node2 on:parent]; - [node2 release]; - - node2 = [[NSXMLElement alloc] initWithName:@"boolean"]; - [node2 setAttributesAsDictionary: - @{ @"id": @"chooseRandomImages", - @"_label": @ PHOTOS, - @"arg-set": @"-choose-random-images", - }]; - [self makeCheckbox:node2 on:parent]; - [node2 release]; - - node2 = [[NSXMLElement alloc] initWithName:@"string"]; - [node2 setAttributesAsDictionary: - @{ @"id": @"imageDirectory", - @"_label": @"Images from:", - @"arg": @"-image-directory %", - }]; - [self makeFileSelector:node2 on:parent - dirsOnly:YES withLabel:YES editable:YES]; - [node2 release]; - -# undef SCREENS -# undef PHOTOS - -# ifndef USE_IPHONE - // Add a second, explanatory label below the file/URL selector. - - LABEL *lab2 = 0; - lab2 = [self makeLabel:@"(Local folder, or URL of RSS or Atom feed)"]; - [self placeChild:lab2 on:parent]; - - // Pack it in a little tighter vertically. - NSRect r2 = [lab2 frame]; - r2.origin.x += 20; - r2.origin.y += 14; - [lab2 setFrameOrigin:r2.origin]; -# endif // USE_IPHONE -} - - -- (void) makeUpdaterControlBox:(NSXMLNode *)node on:(NSView *)parent -{ -# ifndef USE_IPHONE - /* - [x] Check for Updates [ Monthly ] - - <hgroup> - <boolean id="automaticallyChecksForUpdates" - _label="Automatically check for updates" - arg-unset="-no-automaticallyChecksForUpdates" /> - <select id="updateCheckInterval"> - <option="hourly" _label="Hourly" arg-set="-updateCheckInterval 3600"/> - <option="daily" _label="Daily" arg-set="-updateCheckInterval 86400"/> - <option="weekly" _label="Weekly" arg-set="-updateCheckInterval 604800"/> - <option="monthly" _label="Monthly" arg-set="-updateCheckInterval 2629800"/> - </select> - </hgroup> - */ - - // <hgroup> - - NSRect rect; - rect.size.width = rect.size.height = 1; - rect.origin.x = rect.origin.y = 0; - NSView *group = [[NSView alloc] initWithFrame:rect]; - - NSXMLElement *node2; - - // <boolean ...> - - node2 = [[NSXMLElement alloc] initWithName:@"boolean"]; - [node2 setAttributesAsDictionary: - @{ @"id": @SUSUEnableAutomaticChecksKey, - @"_label": @"Automatically check for updates", - @"arg-unset": @"-no-" SUSUEnableAutomaticChecksKey, - }]; - [self makeCheckbox:node2 on:group]; - [node2 release]; - - // <select ...> - - node2 = [[NSXMLElement alloc] initWithName:@"select"]; - [node2 setAttributesAsDictionary: - @{ @"id": @SUScheduledCheckIntervalKey }]; - - // <option ...> - - NSXMLNode *node3 = [[NSXMLElement alloc] initWithName:@"option"]; - [node3 setAttributesAsDictionary: - @{ @"id": @"hourly", - @"arg-set": @"-" SUScheduledCheckIntervalKey " 3600", - @"_label": @"Hourly" }]; - [node3 setParent: node2]; - [node3 autorelease]; - - node3 = [[NSXMLElement alloc] initWithName:@"option"]; - [node3 setAttributesAsDictionary: - @{ @"id": @"daily", - @"arg-set": @"-" SUScheduledCheckIntervalKey " 86400", - @"_label": @"Daily" }]; - [node3 setParent: node2]; - [node3 autorelease]; - - node3 = [[NSXMLElement alloc] initWithName:@"option"]; - [node3 setAttributesAsDictionary: - @{ @"id": @"weekly", - // @"arg-set": @"-" SUScheduledCheckIntervalKey " 604800", - @"_label": @"Weekly", - }]; - [node3 setParent: node2]; - [node3 autorelease]; - - node3 = [[NSXMLElement alloc] initWithName:@"option"]; - [node3 setAttributesAsDictionary: - @{ @"id": @"monthly", - @"arg-set": @"-" SUScheduledCheckIntervalKey " 2629800", - @"_label": @"Monthly", - }]; - [node3 setParent: node2]; - [node3 autorelease]; - - // </option> - [self makeOptionMenu:node2 on:group]; - [node2 release]; - - // </hgroup> - layout_group (group, TRUE); - - rect.size.width = rect.size.height = 0; - NSBox *box = [[NSBox alloc] initWithFrame:rect]; - [box setTitlePosition:NSNoTitle]; - [box setBorderType:NSNoBorder]; - [box setContentViewMargins:rect.size]; - [box setContentView:group]; - [box sizeToFit]; - - [self placeChild:box on:parent]; - - [group release]; - [box release]; - -# endif // !USE_IPHONE -} - - -#pragma mark Layout for controls - - -# ifndef USE_IPHONE -static NSView * -last_child (NSView *parent) -{ - NSArray *kids = [parent subviews]; - NSUInteger nkids = [kids count]; - if (nkids == 0) - return 0; - else - return [kids objectAtIndex:nkids-1]; -} -#endif // USE_IPHONE - - -/* Add the child as a subview of the parent, positioning it immediately - below or to the right of the previously-added child of that view. - */ -- (void) placeChild: -# ifdef USE_IPHONE - (NSObject *)child -# else - (NSView *)child -# endif - on:(NSView *)parent right:(BOOL)right_p -{ -# ifndef USE_IPHONE - NSRect rect = [child frame]; - NSView *last = last_child (parent); - if (!last) { - rect.origin.x = LEFT_MARGIN; - rect.origin.y = ([parent frame].size.height - rect.size.height - - LINE_SPACING); - } else if (right_p) { - rect = [last frame]; - rect.origin.x += rect.size.width + COLUMN_SPACING; - } else { - rect = [last frame]; - rect.origin.x = LEFT_MARGIN; - rect.origin.y -= [child frame].size.height + LINE_SPACING; - } - NSRect r = [child frame]; - r.origin = rect.origin; - [child setFrame:r]; - [parent addSubview:child]; - -# else // USE_IPHONE - - /* Controls is an array of arrays of the controls, divided into sections. - Each hgroup / vgroup gets a nested array, too, e.g.: - - [ [ [ <label>, <checkbox> ], - [ <label>, <checkbox> ], - [ <label>, <checkbox> ] ], - [ <label>, <text-field> ], - [ <label>, <low-label>, <slider>, <high-label> ], - [ <low-label>, <slider>, <high-label> ], - <HTML-label> - ]; - - If an element begins with a label, it is terminal, otherwise it is a - group. There are (currently) never more than 4 elements in a single - terminal element. - - A blank vertical spacer is placed between each hgroup / vgroup, - by making each of those a new section in the TableView. - */ - if (! controls) - controls = [[NSMutableArray arrayWithCapacity:10] retain]; - if ([controls count] == 0) - [controls addObject: [NSMutableArray arrayWithCapacity:10]]; - NSMutableArray *current = [controls objectAtIndex:[controls count]-1]; - - if (!right_p || [current count] == 0) { - // Nothing on the current line. Add this object. - [current addObject: child]; - } else { - // Something's on the current line already. - NSObject *old = [current objectAtIndex:[current count]-1]; - if ([old isKindOfClass:[NSMutableArray class]]) { - // Already an array in this cell. Append. - NSAssert ([(NSArray *) old count] < 4, @"internal error"); - [(NSMutableArray *) old addObject: child]; - } else { - // Replace the control in this cell with an array, then append - NSMutableArray *a = [NSMutableArray arrayWithObjects: old, child, nil]; - [current replaceObjectAtIndex:[current count]-1 withObject:a]; - } - } -# endif // USE_IPHONE -} - - -- (void) placeChild:(NSView *)child on:(NSView *)parent -{ - [self placeChild:child on:parent right:NO]; -} - - -#ifdef USE_IPHONE - -// Start putting subsequent children in a new group, to create a new -// section on the UITableView. -// -- (void) placeSeparator -{ - if (! controls) return; - if ([controls count] == 0) return; - if ([[controls objectAtIndex:[controls count]-1] - count] > 0) - [controls addObject: [NSMutableArray arrayWithCapacity:10]]; -} -#endif // USE_IPHONE - - - -/* Creates an invisible NSBox (for layout purposes) to enclose the widgets - wrapped in <hgroup> or <vgroup> in the XML. - */ -- (void) makeGroup:(NSXMLNode *)node - on:(NSView *)parent - horizontal:(BOOL) horiz_p -{ -# ifdef USE_IPHONE - if (!horiz_p) [self placeSeparator]; - [self traverseChildren:node on:parent]; - if (!horiz_p) [self placeSeparator]; -# else // !USE_IPHONE - NSRect rect; - rect.size.width = rect.size.height = 1; - rect.origin.x = rect.origin.y = 0; - NSView *group = [[NSView alloc] initWithFrame:rect]; - [self traverseChildren:node on:group]; - - layout_group (group, horiz_p); - - rect.size.width = rect.size.height = 0; - NSBox *box = [[NSBox alloc] initWithFrame:rect]; - [box setTitlePosition:NSNoTitle]; - [box setBorderType:NSNoBorder]; - [box setContentViewMargins:rect.size]; - [box setContentView:group]; - [box sizeToFit]; - - [self placeChild:box on:parent]; - [group release]; - [box release]; -# endif // !USE_IPHONE -} - - -#ifndef USE_IPHONE -static void -layout_group (NSView *group, BOOL horiz_p) -{ - NSArray *kids = [group subviews]; - NSUInteger nkids = [kids count]; - NSUInteger i; - double maxx = 0, miny = 0; - for (i = 0; i < nkids; i++) { - NSView *kid = [kids objectAtIndex:i]; - NSRect r = [kid frame]; - - if (horiz_p) { - maxx += r.size.width + COLUMN_SPACING; - if (r.size.height > -miny) miny = -r.size.height; - } else { - if (r.size.width > maxx) maxx = r.size.width; - miny = r.origin.y - r.size.height; - } - } - - NSRect rect; - rect.origin.x = 0; - rect.origin.y = 0; - rect.size.width = maxx; - rect.size.height = -miny; - [group setFrame:rect]; - - double x = 0; - for (i = 0; i < nkids; i++) { - NSView *kid = [kids objectAtIndex:i]; - NSRect r = [kid frame]; - if (horiz_p) { - r.origin.y = rect.size.height - r.size.height; - r.origin.x = x; - x += r.size.width + COLUMN_SPACING; - } else { - r.origin.y -= miny; - } - [kid setFrame:r]; - } -} -#endif // !USE_IPHONE - - -/* Create some kind of control corresponding to the given XML node. - */ --(void)makeControl:(NSXMLNode *)node on:(NSView *)parent -{ - NSString *name = [node name]; - - if ([node kind] == NSXMLCommentKind) - return; - - if ([node kind] == NSXMLTextKind) { - NSString *s = [(NSString *) [node objectValue] - stringByTrimmingCharactersInSet: - [NSCharacterSet whitespaceAndNewlineCharacterSet]]; - if (! [s isEqualToString:@""]) { - NSAssert1 (0, @"unexpected text: %@", s); - } - return; - } - - if ([node kind] != NSXMLElementKind) { - NSAssert2 (0, @"weird XML node kind: %d: %@", (int)[node kind], node); - return; - } - - if ([name isEqualToString:@"hgroup"] || - [name isEqualToString:@"vgroup"]) { - - [self makeGroup:node on:parent - horizontal:[name isEqualToString:@"hgroup"]]; - - } else if ([name isEqualToString:@"command"]) { - // do nothing: this is the "-root" business - - } else if ([name isEqualToString:@"video"]) { - // ignored - - } else if ([name isEqualToString:@"boolean"]) { - [self makeCheckbox:node on:parent]; - - } else if ([name isEqualToString:@"string"]) { - [self makeTextField:node on:parent withLabel:NO horizontal:NO]; - - } else if ([name isEqualToString:@"file"]) { - [self makeFileSelector:node on:parent - dirsOnly:NO withLabel:YES editable:NO]; - - } else if ([name isEqualToString:@"number"]) { - [self makeNumberSelector:node on:parent]; - - } else if ([name isEqualToString:@"select"]) { - [self makeOptionMenu:node on:parent]; - - } else if ([name isEqualToString:@"_description"]) { - [self makeDescLabel:node on:parent]; - - } else if ([name isEqualToString:@"xscreensaver-text"]) { - [self makeTextLoaderControlBox:node on:parent]; - - } else if ([name isEqualToString:@"xscreensaver-image"]) { - [self makeImageLoaderControlBox:node on:parent]; - - } else if ([name isEqualToString:@"xscreensaver-updater"]) { - [self makeUpdaterControlBox:node on:parent]; - - } else { - NSAssert1 (0, @"unknown tag: %@", name); - } -} - - -/* Iterate over and process the children of this XML node. - */ -- (void)traverseChildren:(NSXMLNode *)node on:(NSView *)parent -{ - NSArray *children = [node children]; - NSUInteger i, count = [children count]; - for (i = 0; i < count; i++) { - NSXMLNode *child = [children objectAtIndex:i]; - [self makeControl:child on:parent]; - } -} - - -# ifndef USE_IPHONE - -/* Kludgey magic to make the window enclose the controls we created. - */ -static void -fix_contentview_size (NSView *parent) -{ - NSRect f; - NSArray *kids = [parent subviews]; - NSUInteger nkids = [kids count]; - NSView *text = 0; // the NSText at the bottom of the window - double maxx = 0, miny = 0; - NSUInteger i; - - /* Find the size of the rectangle taken up by each of the children - except the final "NSText" child. - */ - for (i = 0; i < nkids; i++) { - NSView *kid = [kids objectAtIndex:i]; - if ([kid isKindOfClass:[NSText class]]) { - text = kid; - continue; - } - f = [kid frame]; - if (f.origin.x + f.size.width > maxx) maxx = f.origin.x + f.size.width; - if (f.origin.y - f.size.height < miny) miny = f.origin.y; -// NSLog(@"start: %3.0f x %3.0f @ %3.0f %3.0f %3.0f %@", -// f.size.width, f.size.height, f.origin.x, f.origin.y, -// f.origin.y + f.size.height, [kid class]); - } - - if (maxx < 400) maxx = 400; // leave room for the NSText paragraph... - - /* Now that we know the width of the window, set the width of the NSText to - that, so that it can decide what its height needs to be. - */ - if (! text) abort(); - f = [text frame]; -// NSLog(@"text old: %3.0f x %3.0f @ %3.0f %3.0f %3.0f %@", -// f.size.width, f.size.height, f.origin.x, f.origin.y, -// f.origin.y + f.size.height, [text class]); - - // set the NSText's width (this changes its height). - f.size.width = maxx - LEFT_MARGIN; - [text setFrame:f]; - - // position the NSText below the last child (this gives us a new miny). - f = [text frame]; - f.origin.y = miny - f.size.height - LINE_SPACING; - miny = f.origin.y - LINE_SPACING; - [text setFrame:f]; - - // Lock the width of the field and unlock the height, and let it resize - // once more, to compute the proper height of the text for that width. - // - [(NSText *) text setHorizontallyResizable:NO]; - [(NSText *) text setVerticallyResizable:YES]; - [(NSText *) text sizeToFit]; - - // Now lock the height too: no more resizing this text field. - // - [(NSText *) text setVerticallyResizable:NO]; - - // Now reposition the top edge of the text field to be back where it - // was before we changed the height. - // - float oh = f.size.height; - f = [text frame]; - float dh = f.size.height - oh; - f.origin.y += dh; - - // #### This is needed in OSX 10.5, but is wrong in OSX 10.6. WTF?? - // If we do this in 10.6, the text field moves down, off the window. - // So instead we repair it at the end, at the "WTF2" comment. - [text setFrame:f]; - - // Also adjust the parent height by the change in height of the text field. - miny -= dh; - -// NSLog(@"text new: %3.0f x %3.0f @ %3.0f %3.0f %3.0f %@", -// f.size.width, f.size.height, f.origin.x, f.origin.y, -// f.origin.y + f.size.height, [text class]); - - - /* Set the contentView to the size of the children. - */ - f = [parent frame]; -// float yoff = f.size.height; - f.size.width = maxx + LEFT_MARGIN; - f.size.height = -(miny - LEFT_MARGIN*2); -// yoff = f.size.height - yoff; - [parent setFrame:f]; - -// NSLog(@"max: %3.0f x %3.0f @ %3.0f %3.0f", -// f.size.width, f.size.height, f.origin.x, f.origin.y); - - /* Now move all of the kids up into the window. - */ - f = [parent frame]; - float shift = f.size.height; -// NSLog(@"shift: %3.0f", shift); - for (i = 0; i < nkids; i++) { - NSView *kid = [kids objectAtIndex:i]; - f = [kid frame]; - f.origin.y += shift; - [kid setFrame:f]; -// NSLog(@"move: %3.0f x %3.0f @ %3.0f %3.0f %3.0f %@", -// f.size.width, f.size.height, f.origin.x, f.origin.y, -// f.origin.y + f.size.height, [kid class]); - } - -/* - Bad: - parent: 420 x 541 @ 0 0 - text: 380 x 100 @ 20 22 miny=-501 - - Good: - parent: 420 x 541 @ 0 0 - text: 380 x 100 @ 20 50 miny=-501 - */ - - // #### WTF2: See "WTF" above. If the text field is off the screen, - // move it up. We need this on 10.6 but not on 10.5. Auugh. - // - f = [text frame]; - if (f.origin.y < 50) { // magic numbers, yay - f.origin.y = 50; - [text setFrame:f]; - } - - /* Set the kids to track the top left corner of the window when resized. - Set the NSText to track the bottom right corner as well. - */ - for (i = 0; i < nkids; i++) { - NSView *kid = [kids objectAtIndex:i]; - unsigned long mask = NSViewMaxXMargin | NSViewMinYMargin; - if ([kid isKindOfClass:[NSText class]]) - mask |= NSViewWidthSizable|NSViewHeightSizable; - [kid setAutoresizingMask:mask]; - } -} -# endif // !USE_IPHONE - - - -#ifndef USE_IPHONE -static NSView * -wrap_with_buttons (NSWindow *window, NSView *panel) -{ - NSRect rect; - - // Make a box to hold the buttons at the bottom of the window. - // - rect = [panel frame]; - rect.origin.x = rect.origin.y = 0; - rect.size.height = 10; - NSBox *bbox = [[NSBox alloc] initWithFrame:rect]; - [bbox setTitlePosition:NSNoTitle]; - [bbox setBorderType:NSNoBorder]; - - // Make some buttons: Default, Cancel, OK - // - rect.origin.x = rect.origin.y = 0; - rect.size.width = rect.size.height = 10; - NSButton *reset = [[NSButton alloc] initWithFrame:rect]; - [reset setTitle:@"Reset to Defaults"]; - [reset setBezelStyle:NSRoundedBezelStyle]; - [reset sizeToFit]; - - rect = [reset frame]; - NSButton *ok = [[NSButton alloc] initWithFrame:rect]; - [ok setTitle:@"OK"]; - [ok setBezelStyle:NSRoundedBezelStyle]; - [ok sizeToFit]; - rect = [bbox frame]; - rect.origin.x = rect.size.width - [ok frame].size.width; - [ok setFrameOrigin:rect.origin]; - - rect = [ok frame]; - NSButton *cancel = [[NSButton alloc] initWithFrame:rect]; - [cancel setTitle:@"Cancel"]; - [cancel setBezelStyle:NSRoundedBezelStyle]; - [cancel sizeToFit]; - rect.origin.x -= [cancel frame].size.width + 10; - [cancel setFrameOrigin:rect.origin]; - - // Bind OK to RET and Cancel to ESC. - [ok setKeyEquivalent:@"\r"]; - [cancel setKeyEquivalent:@"\e"]; - - // The correct width for OK and Cancel buttons is 68 pixels - // ("Human Interface Guidelines: Controls: Buttons: - // Push Button Specifications"). - // - rect = [ok frame]; - rect.size.width = 68; - [ok setFrame:rect]; - - rect = [cancel frame]; - rect.size.width = 68; - [cancel setFrame:rect]; - - // It puts the buttons in the box or else it gets the hose again - // - [bbox addSubview:ok]; - [bbox addSubview:cancel]; - [bbox addSubview:reset]; - [bbox sizeToFit]; - - // make a box to hold the button-box, and the preferences view - // - rect = [bbox frame]; - rect.origin.y += rect.size.height; - NSBox *pbox = [[NSBox alloc] initWithFrame:rect]; - [pbox setTitlePosition:NSNoTitle]; - [pbox setBorderType:NSBezelBorder]; - - // Enforce a max height on the dialog, so that it's obvious to me - // (on a big screen) when the dialog will fall off the bottom of - // a small screen (e.g., 1024x768 laptop with a huge bottom dock). - { - NSRect f = [panel frame]; - int screen_height = (768 // shortest "modern" Mac display - - 22 // menu bar - - 56 // System Preferences toolbar - - 140 // default magnified bottom dock icon - ); - if (f.size.height > screen_height) { - NSLog(@"%@ height was %.0f; clipping to %d", - [panel class], f.size.height, screen_height); - f.size.height = screen_height; - [panel setFrame:f]; - } - } - - [pbox addSubview:panel]; - [pbox addSubview:bbox]; - [pbox sizeToFit]; - - [reset setAutoresizingMask:NSViewMaxXMargin]; - [cancel setAutoresizingMask:NSViewMinXMargin]; - [ok setAutoresizingMask:NSViewMinXMargin]; - [bbox setAutoresizingMask:NSViewWidthSizable]; - - // grab the clicks - // - [ok setTarget:window]; - [cancel setTarget:window]; - [reset setTarget:window]; - [ok setAction:@selector(okAction:)]; - [cancel setAction:@selector(cancelAction:)]; - [reset setAction:@selector(resetAction:)]; - - [bbox release]; - - return pbox; -} -#endif // !USE_IPHONE - - -/* Iterate over and process the children of the root node of the XML document. - */ -- (void)traverseTree -{ -# ifdef USE_IPHONE - NSView *parent = [self view]; -# else - NSWindow *parent = self; -#endif - NSXMLNode *node = xml_root; - - if (![[node name] isEqualToString:@"screensaver"]) { - NSAssert (0, @"top level node is not <xscreensaver>"); - } - - saver_name = [self parseXScreenSaverTag: node]; - saver_name = [saver_name stringByReplacingOccurrencesOfString:@" " - withString:@""]; - [saver_name retain]; - -# ifndef USE_IPHONE - - NSRect rect; - rect.origin.x = rect.origin.y = 0; - rect.size.width = rect.size.height = 1; - - NSView *panel = [[NSView alloc] initWithFrame:rect]; - [self traverseChildren:node on:panel]; - fix_contentview_size (panel); - - NSView *root = wrap_with_buttons (parent, panel); - [userDefaultsController setAppliesImmediately:NO]; - [globalDefaultsController setAppliesImmediately:NO]; - - [panel setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; - - rect = [parent frameRectForContentRect:[root frame]]; - [parent setFrame:rect display:NO]; - [parent setMinSize:rect.size]; - - [parent setContentView:root]; - - [panel release]; - [root release]; - -# else // USE_IPHONE - - CGRect r = [parent frame]; - r.size = [[UIScreen mainScreen] bounds].size; - [parent setFrame:r]; - [self traverseChildren:node on:parent]; - -# endif // USE_IPHONE -} - - -- (void)parser:(NSXMLParser *)parser - didStartElement:(NSString *)elt - namespaceURI:(NSString *)ns - qualifiedName:(NSString *)qn - attributes:(NSDictionary *)attrs -{ - NSXMLElement *e = [[NSXMLElement alloc] initWithName:elt]; - [e autorelease]; - [e setKind:SimpleXMLElementKind]; - [e setAttributesAsDictionary:attrs]; - NSXMLElement *p = xml_parsing; - [e setParent:p]; - xml_parsing = e; - if (! xml_root) - xml_root = xml_parsing; -} - -- (void)parser:(NSXMLParser *)parser - didEndElement:(NSString *)elt - namespaceURI:(NSString *)ns - qualifiedName:(NSString *)qn -{ - NSXMLElement *p = xml_parsing; - if (! p) { - NSLog(@"extra close: %@", elt); - } else if (![[p name] isEqualToString:elt]) { - NSLog(@"%@ closed by %@", [p name], elt); - } else { - NSXMLElement *n = xml_parsing; - xml_parsing = [n parent]; - } -} - - -- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string -{ - NSXMLElement *e = [[NSXMLElement alloc] initWithName:@"text"]; - [e setKind:SimpleXMLTextKind]; - NSXMLElement *p = xml_parsing; - [e setParent:p]; - [e setObjectValue: string]; - [e autorelease]; -} - - -# ifdef USE_IPHONE -# ifdef USE_PICKER_VIEW - -#pragma mark UIPickerView delegate methods - -- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pv -{ - return 1; // Columns -} - -- (NSInteger)pickerView:(UIPickerView *)pv - numberOfRowsInComponent:(NSInteger)column -{ - NSAssert (column == 0, @"weird column"); - NSArray *a = [picker_values objectAtIndex: [pv tag]]; - if (! a) return 0; // Too early? - return [a count]; -} - -- (CGFloat)pickerView:(UIPickerView *)pv - rowHeightForComponent:(NSInteger)column -{ - return FONT_SIZE; -} - -- (CGFloat)pickerView:(UIPickerView *)pv - widthForComponent:(NSInteger)column -{ - NSAssert (column == 0, @"weird column"); - NSArray *a = [picker_values objectAtIndex: [pv tag]]; - if (! a) return 0; // Too early? - - UIFont *f = [UIFont systemFontOfSize:[NSFont systemFontSize]]; - CGFloat max = 0; - for (NSArray *a2 in a) { - NSString *s = [a2 objectAtIndex:0]; - // #### sizeWithFont deprecated as of iOS 7; use boundingRectWithSize. - CGSize r = [s sizeWithFont:f]; - if (r.width > max) max = r.width; - } - - max *= 1.7; // WTF!! - - if (max > 320) - max = 320; - else if (max < 120) - max = 120; - - return max; - -} - - -- (NSString *)pickerView:(UIPickerView *)pv - titleForRow:(NSInteger)row - forComponent:(NSInteger)column -{ - NSAssert (column == 0, @"weird column"); - NSArray *a = [picker_values objectAtIndex: [pv tag]]; - if (! a) return 0; // Too early? - a = [a objectAtIndex:row]; - NSAssert (a, @"internal error"); - return [a objectAtIndex:0]; -} - -# endif // USE_PICKER_VIEW - - -#pragma mark UITableView delegate methods - -- (void) addResetButton -{ - [[self navigationItem] - setRightBarButtonItem: [[UIBarButtonItem alloc] - initWithTitle: @"Reset to Defaults" - style: UIBarButtonItemStylePlain - target:self - action:@selector(resetAction:)]]; - NSString *s = saver_name; - if ([self view].frame.size.width > 320) - s = [s stringByAppendingString: @" Settings"]; - [self navigationItem].title = s; -} - - -- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)o -{ - return YES; -} - -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tv { - // Number of vertically-stacked white boxes. - return [controls count]; -} - -- (NSInteger)tableView:(UITableView *)tableView - numberOfRowsInSection:(NSInteger)section -{ - // Number of lines in each vertically-stacked white box. - NSAssert (controls, @"internal error"); - return [[controls objectAtIndex:section] count]; -} - -- (NSString *)tableView:(UITableView *)tv - titleForHeaderInSection:(NSInteger)section -{ - // Titles above each vertically-stacked white box. -// if (section == 0) -// return [saver_name stringByAppendingString:@" Settings"]; - return nil; -} - - -- (CGFloat)tableView:(UITableView *)tv - heightForRowAtIndexPath:(NSIndexPath *)ip -{ - CGFloat h = 0; - - NSView *ctl = [[controls objectAtIndex:[ip indexAtPosition:0]] - objectAtIndex:[ip indexAtPosition:1]]; - - if ([ctl isKindOfClass:[NSArray class]]) { - NSArray *set = (NSArray *) ctl; - switch ([set count]) { - case 4: // label + left/slider/right. - case 3: // left/slider/right. - h = FONT_SIZE * 3.0; - break; - case 2: // Checkboxes, or text fields. - h = FONT_SIZE * 2.4; - break; - } - } else if ([ctl isKindOfClass:[UILabel class]]) { - // Radio buttons in a multi-select list. - h = FONT_SIZE * 1.9; - -# ifdef USE_HTML_LABELS - } else if ([ctl isKindOfClass:[HTMLLabel class]]) { - - HTMLLabel *t = (HTMLLabel *) ctl; - CGRect r = t.frame; - r.size.width = [tv frame].size.width; - r.size.width -= LEFT_MARGIN * 2; - [t setFrame:r]; - [t sizeToFit]; - r = t.frame; - h = r.size.height; -# endif // USE_HTML_LABELS - - } else { // Does this ever happen? - h = FONT_SIZE + LINE_SPACING * 2; - } - - if (h <= 0) abort(); - return h; -} - - -- (void)refreshTableView -{ - UITableView *tv = (UITableView *) [self view]; - NSMutableArray *a = [NSMutableArray arrayWithCapacity:20]; - NSInteger rows = [self numberOfSectionsInTableView:tv]; - for (int i = 0; i < rows; i++) { - NSInteger cols = [self tableView:tv numberOfRowsInSection:i]; - for (int j = 0; j < cols; j++) { - NSUInteger ip[2]; - ip[0] = i; - ip[1] = j; - [a addObject: [NSIndexPath indexPathWithIndexes:ip length:2]]; - } - } - - [tv beginUpdates]; - [tv reloadRowsAtIndexPaths:a withRowAnimation:UITableViewRowAnimationNone]; - [tv endUpdates]; - - // Default opacity looks bad. - // #### Oh great, this only works *sometimes*. - UIView *v = [[self navigationItem] titleView]; - [v setBackgroundColor:[[v backgroundColor] colorWithAlphaComponent:1]]; -} - - -- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)o -{ - [NSTimer scheduledTimerWithTimeInterval: 0 - target:self - selector:@selector(refreshTableView) - userInfo:nil - repeats:NO]; -} - - -#ifndef USE_PICKER_VIEW - -- (void)updateRadioGroupCell:(UITableViewCell *)cell - button:(RadioButton *)b -{ - NSArray *item = [[b items] objectAtIndex: [b index]]; - NSString *pref_key = [item objectAtIndex:1]; - NSObject *pref_val = [item objectAtIndex:2]; - - NSObject *current = [[self controllerForKey:pref_key] objectForKey:pref_key]; - - // Convert them both to strings and compare those, so that - // we don't get screwed by int 1 versus string "1". - // Will boolean true/1 screw us here too? - // - NSString *pref_str = ([pref_val isKindOfClass:[NSString class]] - ? (NSString *) pref_val - : [(NSNumber *) pref_val stringValue]); - NSString *current_str = ([current isKindOfClass:[NSString class]] - ? (NSString *) current - : [(NSNumber *) current stringValue]); - BOOL match_p = [current_str isEqualToString:pref_str]; - - // NSLog(@"\"%@\" = \"%@\" | \"%@\" ", pref_key, pref_val, current_str); - - if (match_p) - [cell setAccessoryType:UITableViewCellAccessoryCheckmark]; - else - [cell setAccessoryType:UITableViewCellAccessoryNone]; -} - - -- (void)tableView:(UITableView *)tv - didSelectRowAtIndexPath:(NSIndexPath *)ip -{ - RadioButton *ctl = [[controls objectAtIndex:[ip indexAtPosition:0]] - objectAtIndex:[ip indexAtPosition:1]]; - if (! [ctl isKindOfClass:[RadioButton class]]) - return; - - [self radioAction:ctl]; - [self refreshTableView]; -} - - -#endif // !USE_PICKER_VIEW - - - -- (UITableViewCell *)tableView:(UITableView *)tv - cellForRowAtIndexPath:(NSIndexPath *)ip -{ - CGFloat ww = [tv frame].size.width; - CGFloat hh = [self tableView:tv heightForRowAtIndexPath:ip]; - - float os_version = [[[UIDevice currentDevice] systemVersion] floatValue]; - - // Width of the column of labels on the left. - CGFloat left_width = ww * 0.4; - CGFloat right_edge = ww - LEFT_MARGIN; - - if (os_version < 7) // margins were wider on iOS 6.1 - right_edge -= 10; - - CGFloat max = FONT_SIZE * 12; - if (left_width > max) left_width = max; - - NSView *ctl = [[controls objectAtIndex:[ip indexAtPosition:0]] - objectAtIndex:[ip indexAtPosition:1]]; - - if ([ctl isKindOfClass:[NSArray class]]) { - // This cell has a set of objects in it. - NSArray *set = (NSArray *) ctl; - switch ([set count]) { - case 2: - { - // With 2 elements, the first of the pair must be a label. - UILabel *label = (UILabel *) [set objectAtIndex: 0]; - NSAssert ([label isKindOfClass:[UILabel class]], @"unhandled type"); - ctl = [set objectAtIndex: 1]; - - CGRect r = [ctl frame]; - - if ([ctl isKindOfClass:[UISwitch class]]) { // Checkboxes. - r.size.width = 80; // Magic. - r.origin.x = right_edge - r.size.width + 30; // beats me - - if (os_version < 7) // checkboxes were wider on iOS 6.1 - r.origin.x -= 25; - - } else { - r.origin.x = left_width; // Text fields, etc. - r.size.width = right_edge - r.origin.x; - } - - r.origin.y = (hh - r.size.height) / 2; // Center vertically. - [ctl setFrame:r]; - - // Make a box and put the label and checkbox/slider into it. - r.origin.x = 0; - r.origin.y = 0; - r.size.width = ww; - r.size.height = hh; - NSView *box = [[UIView alloc] initWithFrame:r]; - [box addSubview: ctl]; - - // Let the label make use of any space not taken up by the control. - r = [label frame]; - r.origin.x = LEFT_MARGIN; - r.origin.y = 0; - r.size.width = [ctl frame].origin.x - r.origin.x; - r.size.height = hh; - [label setFrame:r]; - [label setFont:[NSFont boldSystemFontOfSize: FONT_SIZE]]; - [box addSubview: label]; - [box autorelease]; - - ctl = box; - } - break; - case 3: - case 4: - { - // With 3 elements, 1 and 3 are labels. - // With 4 elements, 1, 2 and 4 are labels. - int i = 0; - UILabel *top = ([set count] == 4 - ? [set objectAtIndex: i++] - : 0); - UILabel *left = [set objectAtIndex: i++]; - NSView *mid = [set objectAtIndex: i++]; - UILabel *right = [set objectAtIndex: i++]; - NSAssert (!top || [top isKindOfClass:[UILabel class]], @"WTF"); - NSAssert ( [left isKindOfClass:[UILabel class]], @"WTF"); - NSAssert ( ![mid isKindOfClass:[UILabel class]], @"WTF"); - NSAssert ( [right isKindOfClass:[UILabel class]], @"WTF"); - - // 3 elements: control at top of cell. - // 4 elements: center the control vertically. - CGRect r = [mid frame]; - r.size.height = 32; // Unchangable height of the slider thumb. - - // Center the slider between left_width and right_edge. -# ifdef LABEL_ABOVE_SLIDER - r.origin.x = LEFT_MARGIN; -# else - r.origin.x = left_width; -# endif - r.origin.y = (hh - r.size.height) / 2; - r.size.width = right_edge - r.origin.x; - [mid setFrame:r]; - - if (top) { -# ifdef LABEL_ABOVE_SLIDER - // Top label goes above, flush center/top. - r.origin.x = (ww - r.size.width) / 2; - r.origin.y = 4; - // #### sizeWithFont deprecated as of iOS 7; use boundingRectWithSize. - r.size = [[top text] sizeWithFont:[top font] - constrainedToSize: - CGSizeMake (ww - LEFT_MARGIN*2, 100000) - lineBreakMode:[top lineBreakMode]]; -# else // !LABEL_ABOVE_SLIDER - // Label goes on the left. - r.origin.x = LEFT_MARGIN; - r.origin.y = 0; - r.size.width = left_width - LEFT_MARGIN; - r.size.height = hh; -# endif // !LABEL_ABOVE_SLIDER - [top setFrame:r]; - } - - // Left label goes under control, flush left/bottom. - left.frame = CGRectMake([mid frame].origin.x, hh - 4, - ww - LEFT_MARGIN*2, 100000); - [left sizeToFit]; - r = left.frame; - r.origin.y -= r.size.height; - left.frame = r; - - // Right label goes under control, flush right/bottom. - right.frame = - CGRectMake([mid frame].origin.x + [mid frame].size.width, - [left frame].origin.y, ww - LEFT_MARGIN*2, 1000000); - [right sizeToFit]; - r = right.frame; - r.origin.x -= r.size.width; - right.frame = r; - - // Make a box and put the labels and slider into it. - r.origin.x = 0; - r.origin.y = 0; - r.size.width = ww; - r.size.height = hh; - NSView *box = [[UIView alloc] initWithFrame:r]; - if (top) - [box addSubview: top]; - [box addSubview: left]; - [box addSubview: right]; - [box addSubview: mid]; - [box autorelease]; - - ctl = box; - } - break; - default: - NSAssert (0, @"unhandled size"); - } - } else { // A single view, not a pair. - CGRect r = [ctl frame]; - r.origin.x = LEFT_MARGIN; - r.origin.y = 0; - r.size.width = right_edge - r.origin.x; - r.size.height = hh; - [ctl setFrame:r]; - } - - NSString *id = @"Cell"; - UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:id]; - if (!cell) - cell = [[[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault - reuseIdentifier: id] - autorelease]; - - for (UIView *subview in [cell.contentView subviews]) - [subview removeFromSuperview]; - [cell.contentView addSubview: ctl]; - CGRect r = [ctl frame]; - r.origin.x = 0; - r.origin.y = 0; - [cell setFrame:r]; - cell.selectionStyle = UITableViewCellSelectionStyleNone; - [cell setAccessoryType:UITableViewCellAccessoryNone]; - -# ifndef USE_PICKER_VIEW - if ([ctl isKindOfClass:[RadioButton class]]) - [self updateRadioGroupCell:cell button:(RadioButton *)ctl]; -# endif // USE_PICKER_VIEW - - return cell; -} -# endif // USE_IPHONE - - -/* When this object is instantiated, it parses the XML file and creates - controls on itself that are hooked up to the appropriate preferences. - The default size of the view is just big enough to hold them all. - */ -- (id)initWithXML: (NSData *) xml_data - options: (const XrmOptionDescRec *) _opts - controller: (NSUserDefaultsController *) _prefs - globalController: (NSUserDefaultsController *) _globalPrefs - defaults: (NSDictionary *) _defs -{ -# ifndef USE_IPHONE - self = [super init]; -# else // !USE_IPHONE - self = [super initWithStyle:UITableViewStyleGrouped]; - self.title = [saver_name stringByAppendingString:@" Settings"]; -# endif // !USE_IPHONE - if (! self) return 0; - - // instance variables - opts = _opts; - defaultOptions = _defs; - userDefaultsController = [_prefs retain]; - globalDefaultsController = [_globalPrefs retain]; - - NSXMLParser *xmlDoc = [[NSXMLParser alloc] initWithData:xml_data]; - - if (!xmlDoc) { - NSAssert1 (0, @"XML Error: %@", - [[NSString alloc] initWithData:xml_data - encoding:NSUTF8StringEncoding]); - return nil; - } - [xmlDoc setDelegate:self]; - if (! [xmlDoc parse]) { - NSError *err = [xmlDoc parserError]; - NSAssert2 (0, @"XML Error: %@: %@", - [[NSString alloc] initWithData:xml_data - encoding:NSUTF8StringEncoding], - err); - return nil; - } - -# ifndef USE_IPHONE - TextModeTransformer *t = [[TextModeTransformer alloc] init]; - [NSValueTransformer setValueTransformer:t - forName:@"TextModeTransformer"]; - [t release]; -# endif // USE_IPHONE - - [self traverseTree]; - xml_root = 0; - -# ifdef USE_IPHONE - [self addResetButton]; -# endif - - return self; -} - - -- (void) dealloc -{ - [saver_name release]; - [userDefaultsController release]; - [globalDefaultsController release]; -# ifdef USE_IPHONE - [controls release]; - [pref_keys release]; - [pref_ctls release]; -# ifdef USE_PICKER_VIEW - [picker_values release]; -# endif -# endif - [super dealloc]; -} - -@end |