-/* xscreensaver, Copyright (c) 2006-2012 Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright (c) 2006-2013 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
*/
#import "XScreenSaverConfigSheet.h"
+#import "Updater.h"
#import "jwxyz.h"
#import "InvertedSlider.h"
#endif // USE_IPHONE
#undef LABEL_ABOVE_SLIDER
+#define USE_HTML_LABELS
#pragma mark XML Parser
# 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 = (UIViewAutoresizingFlexibleWidth |
+ UIViewAutoresizingFlexibleHeight);
+ webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth |
+ UIViewAutoresizingFlexibleHeight);
+ [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.
+ "}"
+ "\n//-->\n"
+ "</STYLE>"
+ "</HEAD>"
+ "<BODY>"
+ "%@"
+ "</BODY>"
+ "</HTML>",
+ [font fontName],
+ [font pointSize],
+ [font lineHeight],
+ h];
+ [webView loadHTMLString:h2 baseURL:[NSURL URLWithString:@""]];
+}
+
+
+static char *anchorize (const char *url);
+
+- (void) setText: (NSString *)t
+{
+ 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];
+ }
+ [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];
+ [self setHTML: html];
+ [webView reload];
+}
+
+
+- (NSString *) stripTags:(NSString *)str
+{
+ NSString *result = @"";
+
+ 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])];
+
+ 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];
+ }
+ 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 I can't make that work.
+ Maybe because it loads async?
+ */
+# if 0
+ r.size.height = [[webView
+ stringByEvaluatingJavaScriptFromString:
+ @"document.body.offsetHeight"]
+ doubleValue];
+# else
+ NSString *text = [self stripTags: html];
+ CGSize s = r.size;
+ s.height = 999999;
+ s = [text sizeWithFont: font
+ constrainedToSize: s
+ lineBreakMode:NSLineBreakByWordWrapping];
+
+ // GAAAH. Add one more line, or the UIWebView is still scrollable!
+ // The text is sized right, but it lets you scroll it up anyway.
+ s.height += [font pointSize];
+
+ 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)
# 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
instead, so transform keys to "SAVERNAME.KEY".
NOTE: This is duplicated in PrefsReader.m, cause I suck.
-*/
+ */
- (NSString *) makeKey:(NSString *)key
{
# ifdef USE_IPHONE
}
+- (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.
if ([active_text_field canResignFirstResponder])
[active_text_field resignFirstResponder];
NSString *pref_key = [pref_keys objectAtIndex: [sender tag]];
- double v = [sender value];
- if (v == (int) v)
- [userDefaultsController setInteger:v forKey:pref_key];
- else
- [userDefaultsController setDouble:v forKey:pref_key];
+
+ // Hacky API. See comment in InvertedSlider.m.
+ double v = ([sender isKindOfClass: [InvertedSlider class]]
+ ? [(InvertedSlider *) sender transformedValue]
+ : [sender value]);
+
+ [[self controllerForKey:pref_key]
+ setObject:((v == (int) v)
+ ? [NSNumber numberWithInt:(int) v]
+ : [NSNumber numberWithDouble: v])
+ forKey:pref_key];
}
// Called when a checkbox/switch is bonked.
[active_text_field resignFirstResponder];
NSString *pref_key = [pref_keys objectAtIndex: [sender tag]];
NSString *v = ([sender isOn] ? @"true" : @"false");
- [userDefaultsController setObject:v forKey:pref_key];
+ [[self controllerForKey:pref_key] setObject:v forKey:pref_key];
}
# ifdef USE_PICKER_VIEW
//NSString *label = [a objectAtIndex:0];
NSString *pref_key = [a objectAtIndex:1];
NSObject *pref_val = [a objectAtIndex:2];
- [userDefaultsController setObject:pref_val forKey:pref_key];
+ [[self controllerForKey:pref_key] setObject:pref_val forKey:pref_key];
}
# else // !USE_PICKER_VIEW
NSArray *item = [[sender items] objectAtIndex: [sender index]];
NSString *pref_key = [item objectAtIndex:1];
NSObject *pref_val = [item objectAtIndex:2];
- [userDefaultsController setObject:pref_val forKey:pref_key];
+ [[self controllerForKey:pref_key] setObject:pref_val forKey:pref_key];
}
- (BOOL)textFieldShouldBeginEditing:(UITextField *)tf
{
NSString *pref_key = [pref_keys objectAtIndex: [tf tag]];
NSString *txt = [tf text];
- [userDefaultsController setObject:txt forKey:pref_key];
+ [[self controllerForKey:pref_key] setObject:txt forKey:pref_key];
}
- (BOOL)textFieldShouldReturn:(UITextField *)tf
- (void) okAction:(NSObject *)arg
{
- [userDefaultsController commitEditing];
- [userDefaultsController save:self];
+ [userDefaultsController commitEditing];
+ [globalDefaultsController commitEditing];
+ [userDefaultsController save:self];
+ [globalDefaultsController save:self];
[NSApp endSheet:self returnCode:NSOKButton];
[self close];
}
- (void) cancelAction:(NSObject *)arg
{
- [userDefaultsController revert:self];
+ [userDefaultsController revert:self];
+ [globalDefaultsController revert:self];
[NSApp endSheet:self returnCode:NSCancelButton];
[self close];
}
- (void) resetAction:(NSObject *)arg
{
# ifndef USE_IPHONE
- [userDefaultsController revertToInitialValues:self];
+ [userDefaultsController revertToInitialValues:self];
+ [globalDefaultsController revertToInitialValues:self];
# else // USE_IPHONE
for (NSString *key in defaultOptions) {
NSObject *val = [defaultOptions objectForKey:key];
- [userDefaultsController setObject:val forKey:key];
+ [[self controllerForKey:key] setObject:val forKey:key];
}
for (UIControl *ctl in pref_ctls) {
- (void) bindResource:(NSObject *)control key:(NSString *)pref_key
reload:(BOOL)reload_p
{
+ NSUserDefaultsController *prefs = [self controllerForKey:pref_key];
# ifndef USE_IPHONE
NSString *bindto = ([control isKindOfClass:[NSPopUpButton class]]
? @"selectedObject"
? @"selectedIndex"
: @"value"));
[control bind:bindto
- toObject:userDefaultsController
+ toObject:prefs
withKeyPath:[@"values." stringByAppendingString: pref_key]
options:nil];
# else // USE_IPHONE
SEL sel;
- NSObject *val = [userDefaultsController objectForKey:pref_key];
+ NSObject *val = [prefs objectForKey:pref_key];
NSString *sval = 0;
double dval = 0;
if ([control isKindOfClass:[UISlider class]]) {
sel = @selector(sliderAction:);
- [(UISlider *) control setValue: dval];
+ // Hacky API. See comment in InvertedSlider.m.
+ if ([control isKindOfClass:[InvertedSlider class]])
+ [(InvertedSlider *) control setTransformedValue: dval];
+ else
+ [(UISlider *) control setValue: dval];
} else if ([control isKindOfClass:[UISwitch class]]) {
sel = @selector(switchAction:);
[(UISwitch *) control setOn: ((int) dval != 0)];
# endif // USE_IPHONE
# if 0
- NSObject *def = [[userDefaultsController defaults] objectForKey:pref_key];
+ NSObject *def = [[prefs defaults] objectForKey:pref_key];
NSString *s = [NSString stringWithFormat:@"bind: \"%@\"", pref_key];
s = [s stringByPaddingToLength:18 withString:@" " startingAtIndex:0];
s = [NSString stringWithFormat:@"%@ = \"%@\"", s, def];
// Unwrap lines: delete \n but do not delete \n\n.
//
NSArray *lines = [text componentsSeparatedByString:@"\n"];
- int nlines = [lines count];
+ NSUInteger i, nlines = [lines count];
BOOL eolp = YES;
- int i;
text = @"\n"; // start with one blank line
}
+#if !defined(USE_IPHONE) || !defined(USE_HTML_LABELS)
+
/* Converts any http: URLs in the given text field to clickable links.
*/
static void
# endif
}
+#endif /* !USE_IPHONE || !USE_HTML_LABELS */
+
+
#pragma mark Creating controls from XML
- (void) parseAttrs:(NSMutableDictionary *)dict node:(NSXMLNode *)node
{
NSArray *attrs = [(NSXMLElement *) node attributes];
- int n = [attrs count];
+ NSUInteger n = [attrs count];
int i;
// For each key in the dictionary, fill in the dict with the corresponding
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
}
*/
- (NSString *) parseXScreenSaverTag:(NSXMLNode *)node
{
- NSMutableDictionary *dict =
- [NSMutableDictionary dictionaryWithObjectsAndKeys:
- @"", @"name",
- @"", @"_label",
- @"", @"gl",
- nil];
+ NSMutableDictionary *dict = [@{ @"name": @"",
+ @"_label": @"",
+ @"gl": @"" }
+ mutableCopy];
[self parseAttrs:dict node:node];
NSString *name = [dict objectForKey:@"name"];
NSString *label = [dict objectForKey:@"_label"];
[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
[lab setBackgroundColor:[UIColor clearColor]];
[lab setNumberOfLines:0]; // unlimited
- [lab setLineBreakMode:UILineBreakModeWordWrap];
+ // [lab setLineBreakMode:UILineBreakModeWordWrap];
+ [lab setLineBreakMode:NSLineBreakByTruncatingHead];
[lab setAutoresizingMask: (UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight)];
# endif // USE_IPHONE
*/
- (void) makeCheckbox:(NSXMLNode *)node on:(NSView *)parent
{
- NSMutableDictionary *dict =
- [NSMutableDictionary dictionaryWithObjectsAndKeys:
- @"", @"id",
- @"", @"_label",
- @"", @"arg-set",
- @"", @"arg-unset",
- nil];
+ NSMutableDictionary *dict = [@{ @"id": @"",
+ @"_label": @"",
+ @"arg-set": @"",
+ @"arg-unset": @"" }
+ mutableCopy];
[self parseAttrs:dict node:node];
NSString *label = [dict objectForKey:@"_label"];
NSString *arg_set = [dict objectForKey:@"arg-set"];
/* Creates the number selection control described by the given XML node.
If "type=slider", it's an NSSlider.
If "type=spinbutton", it's a text field with up/down arrows next to it.
-*/
+ */
- (void) makeNumberSelector:(NSXMLNode *)node on:(NSView *)parent
{
- NSMutableDictionary *dict =
- [NSMutableDictionary dictionaryWithObjectsAndKeys:
- @"", @"id",
- @"", @"_label",
- @"", @"_low-label",
- @"", @"_high-label",
- @"", @"type",
- @"", @"arg",
- @"", @"low",
- @"", @"high",
- @"", @"default",
- @"", @"convert",
- nil];
+ NSMutableDictionary *dict = [@{ @"id": @"",
+ @"_label": @"",
+ @"_low-label": @"",
+ @"_high-label": @"",
+ @"type": @"",
+ @"arg": @"",
+ @"low": @"",
+ @"high": @"",
+ @"default": @"",
+ @"convert": @"" }
+ mutableCopy];
[self parseAttrs:dict node:node];
NSString *label = [dict objectForKey:@"_label"];
NSString *low_label = [dict objectForKey:@"_low-label"];
[lab setFrame:rect];
[self placeChild:lab on:parent];
# else // USE_IPHONE
- [lab setTextAlignment: UITextAlignmentRight];
+ [lab setTextAlignment: NSTextAlignmentRight];
[self placeChild:lab on:parent right:(label ? YES : NO)];
# 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];
- int i, count = [children count];
+ NSUInteger i, count = [children count];
if (count <= 0) {
NSAssert1 (0, @"no menu items in \"%@\"", [node name]);
// get the "id" attribute off the <select> tag.
//
- NSMutableDictionary *dict =
- [NSMutableDictionary dictionaryWithObjectsAndKeys:
- @"", @"id",
- nil];
+ NSMutableDictionary *dict = [@{ @"id": @"", } mutableCopy];
[self parseAttrs:dict node:node];
NSRect rect;
// get the "id", "_label", and "arg-set" attrs off of the <option> tags.
//
- NSMutableDictionary *dict2 =
- [NSMutableDictionary dictionaryWithObjectsAndKeys:
- @"", @"id",
- @"", @"_label",
- @"", @"arg-set",
- nil];
+ NSMutableDictionary *dict2 = [@{ @"id": @"",
+ @"_label": @"",
+ @"arg-set": @"" }
+ mutableCopy];
[self parseAttrs:dict2 node:child];
NSString *label = [dict2 objectForKey:@"_label"];
NSString *arg_set = [dict2 objectForKey:@"arg-set"];
[self placeSeparator];
i = 0;
- for (NSArray *item in items) {
- RadioButton *b = [[RadioButton alloc] initWithIndex:i
+ for (__attribute__((unused)) NSArray *item in items) {
+ RadioButton *b = [[RadioButton alloc] initWithIndex: (int)i
items:items];
- [b setFont:[NSFont boldSystemFontOfSize:
- // #### Fucking hardcoded UITableView font size BS!
- 17 // [NSFont systemFontSize]
- ]];
+ [b setLineBreakMode:NSLineBreakByTruncatingHead];
+ [b setFont:[NSFont boldSystemFontOfSize: FONT_SIZE]];
[self placeChild:b on:parent];
i++;
}
{
NSString *text = nil;
NSArray *children = [node children];
- int i, count = [children count];
+ NSUInteger i, count = [children count];
for (i = 0; i < count; i++) {
NSXMLNode *child = [children objectAtIndex:i];
hreffify (lab);
boldify (lab);
[lab sizeToFit];
+
# else // USE_IPHONE
+
+# ifndef USE_HTML_LABELS
+
UILabel *lab = [self makeLabel:text];
[lab setFont:[NSFont systemFontOfSize: [NSFont systemFontSize]]];
hreffify (lab);
+
+# else // USE_HTML_LABELS
+ HTMLLabel *lab = [[HTMLLabel alloc]
+ initWithText:text
+ font:[NSFont systemFontOfSize: [NSFont systemFontSize]]];
+ [lab setFrame:rect];
+ [lab sizeToFit];
+# endif // USE_HTML_LABELS
+
[self placeSeparator];
+
# endif // USE_IPHONE
[self placeChild:lab on:parent];
/* Creates the NSTextField described by the given XML node.
-*/
+ */
- (void) makeTextField: (NSXMLNode *)node
on: (NSView *)parent
withLabel: (BOOL) label_p
horizontal: (BOOL) horiz_p
{
- NSMutableDictionary *dict =
- [NSMutableDictionary dictionaryWithObjectsAndKeys:
- @"", @"id",
- @"", @"_label",
- @"", @"arg",
- nil];
+ NSMutableDictionary *dict = [@{ @"id": @"",
+ @"_label": @"",
+ @"arg": @"" }
+ mutableCopy];
[self parseAttrs:dict node:node];
NSString *label = [dict objectForKey:@"_label"];
NSString *arg = [dict objectForKey:@"arg"];
txt.adjustsFontSizeToFitWidth = YES;
txt.textColor = [UIColor blackColor];
- // #### Fucking hardcoded UITableView font size BS!
- txt.font = [UIFont systemFontOfSize: 17];
+ txt.font = [UIFont systemFontOfSize: FONT_SIZE];
txt.placeholder = @"";
txt.borderStyle = UITextBorderStyleRoundedRect;
- txt.textAlignment = UITextAlignmentRight;
+ txt.textAlignment = NSTextAlignmentRight;
txt.keyboardType = UIKeyboardTypeDefault; // Full kbd
txt.autocorrectionType = UITextAutocorrectionTypeNo;
txt.autocapitalizationType = UITextAutocapitalizationTypeNone;
/* 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
editable: (BOOL) editable_p
{
# ifndef USE_IPHONE // No files. No selectors.
- NSMutableDictionary *dict =
- [NSMutableDictionary dictionaryWithObjectsAndKeys:
- @"", @"id",
- @"", @"_label",
- @"", @"arg",
- nil];
+ NSMutableDictionary *dict = [@{ @"id": @"",
+ @"_label": @"",
+ @"arg": @"" }
+ mutableCopy];
[self parseAttrs:dict node:node];
NSString *label = [dict objectForKey:@"_label"];
NSString *arg = [dict objectForKey:@"arg"];
// </select>
node2 = [[NSXMLElement alloc] initWithName:@"select"];
- [node2 setAttributesAsDictionary:
- [NSDictionary dictionaryWithObjectsAndKeys:
- @"textMode", @"id",
- nil]];
+ [node2 setAttributesAsDictionary:@{ @"id": @"textMode" }];
NSXMLNode *node3 = [[NSXMLElement alloc] initWithName:@"option"];
[node3 setAttributesAsDictionary:
- [NSDictionary dictionaryWithObjectsAndKeys:
- @"date", @"id",
- @"-text-mode date", @"arg-set",
- @"Display the date and time", @"_label",
- nil]];
+ @{ @"id": @"date",
+ @"arg-set": @"-text-mode date",
+ @"_label": @"Display the date and time" }];
[node3 setParent: node2];
//[node3 release];
node3 = [[NSXMLElement alloc] initWithName:@"option"];
[node3 setAttributesAsDictionary:
- [NSDictionary dictionaryWithObjectsAndKeys:
- @"text", @"id",
- @"-text-mode literal", @"arg-set",
- @"Display static text", @"_label",
- nil]];
+ @{ @"id": @"text",
+ @"arg-set": @"-text-mode literal",
+ @"_label": @"Display static text" }];
[node3 setParent: node2];
//[node3 release];
node3 = [[NSXMLElement alloc] initWithName:@"option"];
[node3 setAttributesAsDictionary:
- [NSDictionary dictionaryWithObjectsAndKeys:
- @"url", @"id",
- @"Display the contents of a URL", @"_label",
- nil]];
+ @{ @"id": @"url",
+ @"_label": @"Display the contents of a URL" }];
[node3 setParent: node2];
//[node3 release];
// <string id="textLiteral" _label="" arg-set="-text-literal %"/>
node2 = [[NSXMLElement alloc] initWithName:@"string"];
[node2 setAttributesAsDictionary:
- [NSDictionary dictionaryWithObjectsAndKeys:
- @"textLiteral", @"id",
- @"-text-literal %", @"arg",
+ @{ @"id": @"textLiteral",
+ @"arg": @"-text-literal %",
# ifdef USE_IPHONE
- @"Text to display", @"_label",
+ @"_label": @"Text to display"
# endif
- nil]];
+ }];
[self makeTextField:node2 on:rgroup
# ifndef USE_IPHONE
withLabel:NO
toObject:[matrix cellAtRow:1 column:0]
withKeyPath:@"value"
options:nil];
-*/
+ */
# ifndef USE_IPHONE
// <file id="textFile" _label="" arg-set="-text-file %"/>
node2 = [[NSXMLElement alloc] initWithName:@"string"];
[node2 setAttributesAsDictionary:
- [NSDictionary dictionaryWithObjectsAndKeys:
- @"textFile", @"id",
- @"-text-file %", @"arg",
- nil]];
+ @{ @"id": @"textFile",
+ @"arg": @"-text-file %" }];
[self makeFileSelector:node2 on:rgroup
dirsOnly:NO withLabel:NO editable:NO];
# endif // !USE_IPHONE
// <string id="textURL" _label="" arg-set="text-url %"/>
node2 = [[NSXMLElement alloc] initWithName:@"string"];
[node2 setAttributesAsDictionary:
- [NSDictionary dictionaryWithObjectsAndKeys:
- @"textURL", @"id",
- @"-text-url %", @"arg",
+ @{ @"id": @"textURL",
+ @"arg": @"-text-url %",
# ifdef USE_IPHONE
- @"URL to display", @"_label",
+ @"_label": @"URL to display",
# endif
- nil]];
+ }];
[self makeTextField:node2 on:rgroup
# ifndef USE_IPHONE
withLabel:NO
// <string id="textProgram" _label="" arg-set="text-program %"/>
node2 = [[NSXMLElement alloc] initWithName:@"string"];
[node2 setAttributesAsDictionary:
- [NSDictionary dictionaryWithObjectsAndKeys:
- @"textProgram", @"id",
- @"-text-program %", @"arg",
- nil]];
+ @{ @"id": @"textProgram",
+ @"arg": @"-text-program %",
+ }];
[self makeTextField:node2 on:rgroup withLabel:NO horizontal:NO];
}
- (void) makeImageLoaderControlBox:(NSXMLNode *)node on:(NSView *)parent
{
-# ifndef USE_IPHONE
/*
[x] Grab desktop images
[ ] Choose random image:
NSXMLElement *node2;
+# ifndef USE_IPHONE
+# define SCREENS "Grab desktop images"
+# define PHOTOS "Choose random images"
+# else
+# define SCREENS "Grab screenshots"
+# define PHOTOS "Use photo library"
+# endif
+
node2 = [[NSXMLElement alloc] initWithName:@"boolean"];
[node2 setAttributesAsDictionary:
- [NSDictionary dictionaryWithObjectsAndKeys:
- @"grabDesktopImages", @"id",
- @"Grab desktop images", @"_label",
- @"-no-grab-desktop", @"arg-unset",
- nil]];
+ @{ @"id": @"grabDesktopImages",
+ @"_label": @ SCREENS,
+ @"arg-unset": @"-no-grab-desktop",
+ }];
[self makeCheckbox:node2 on:parent];
node2 = [[NSXMLElement alloc] initWithName:@"boolean"];
[node2 setAttributesAsDictionary:
- [NSDictionary dictionaryWithObjectsAndKeys:
- @"chooseRandomImages", @"id",
- @"Choose random images", @"_label",
- @"-choose-random-images", @"arg-set",
- nil]];
+ @{ @"id": @"chooseRandomImages",
+ @"_label": @ PHOTOS,
+ @"arg-set": @"-choose-random-images",
+ }];
[self makeCheckbox:node2 on:parent];
node2 = [[NSXMLElement alloc] initWithName:@"string"];
[node2 setAttributesAsDictionary:
- [NSDictionary dictionaryWithObjectsAndKeys:
- @"imageDirectory", @"id",
- @"Images from:", @"_label",
- @"-image-directory %", @"arg",
- nil]];
+ @{ @"id": @"imageDirectory",
+ @"_label": @"Images from:",
+ @"arg": @"-image-directory %",
+ }];
[self makeFileSelector:node2 on:parent
dirsOnly:YES withLabel:YES editable:YES];
+# undef SCREENS
+# undef PHOTOS
+
+# ifndef USE_IPHONE
// Add a second, explanatory label below the file/URL selector.
LABEL *lab2 = 0;
r2.origin.y += 14;
[lab2 setFrameOrigin:r2.origin];
[lab2 release];
+# endif // USE_IPHONE
+}
+
+
+- (void) makeUpdaterControlBox:(NSXMLNode *)node on:(NSView *)parent
+{
+# ifndef USE_IPHONE
+ /*
+ [x] Check for Updates [ Monthly ]
+
+ <hgroup>
+ <boolean id="automaticallyChecksForUpdates"
+ _label="Automatically check for updates"
+ arg-unset="-no-automaticallyChecksForUpdates" />
+ <select id="updateCheckInterval">
+ <option="hourly" _label="Hourly" arg-set="-updateCheckInterval 3600"/>
+ <option="daily" _label="Daily" arg-set="-updateCheckInterval 86400"/>
+ <option="weekly" _label="Weekly" arg-set="-updateCheckInterval 604800"/>
+ <option="monthly" _label="Monthly" arg-set="-updateCheckInterval 2629800"/>
+ </select>
+ </hgroup>
+ */
+
+ // <hgroup>
+
+ NSRect rect;
+ rect.size.width = rect.size.height = 1;
+ rect.origin.x = rect.origin.y = 0;
+ NSView *group = [[NSView alloc] initWithFrame:rect];
+
+ NSXMLElement *node2;
+
+ // <boolean ...>
+
+ node2 = [[NSXMLElement alloc] initWithName:@"boolean"];
+ [node2 setAttributesAsDictionary:
+ @{ @"id": @SUSUEnableAutomaticChecksKey,
+ @"_label": @"Automatically check for updates",
+ @"arg-unset": @"-no-" SUSUEnableAutomaticChecksKey,
+ }];
+ [self makeCheckbox:node2 on:group];
+
+ // <select ...>
+
+ node2 = [[NSXMLElement alloc] initWithName:@"select"];
+ [node2 setAttributesAsDictionary:
+ @{ @"id": @SUScheduledCheckIntervalKey }];
+
+ // <option ...>
+
+ NSXMLNode *node3 = [[NSXMLElement alloc] initWithName:@"option"];
+ [node3 setAttributesAsDictionary:
+ @{ @"id": @"hourly",
+ @"arg-set": @"-" SUScheduledCheckIntervalKey " 3600",
+ @"_label": @"Hourly" }];
+ [node3 setParent: node2];
+ //[node3 release];
+
+ node3 = [[NSXMLElement alloc] initWithName:@"option"];
+ [node3 setAttributesAsDictionary:
+ @{ @"id": @"daily",
+ @"arg-set": @"-" SUScheduledCheckIntervalKey " 86400",
+ @"_label": @"Daily" }];
+ [node3 setParent: node2];
+ //[node3 release];
+
+ node3 = [[NSXMLElement alloc] initWithName:@"option"];
+ [node3 setAttributesAsDictionary:
+ @{ @"id": @"weekly",
+ // @"arg-set": @"-" SUScheduledCheckIntervalKey " 604800",
+ @"_label": @"Weekly",
+ }];
+ [node3 setParent: node2];
+ //[node3 release];
+
+ node3 = [[NSXMLElement alloc] initWithName:@"option"];
+ [node3 setAttributesAsDictionary:
+ @{ @"id": @"monthly",
+ @"arg-set": @"-" SUScheduledCheckIntervalKey " 2629800",
+ @"_label": @"Monthly",
+ }];
+ [node3 setParent: node2];
+ //[node3 release];
+
+ // </option>
+ [self makeOptionMenu:node2 on:group];
+
+ // </hgroup>
+ layout_group (group, TRUE);
+
+ rect.size.width = rect.size.height = 0;
+ NSBox *box = [[NSBox alloc] initWithFrame:rect];
+ [box setTitlePosition:NSNoTitle];
+ [box setBorderType:NSNoBorder];
+ [box setContentViewMargins:rect.size];
+ [box setContentView:group];
+ [box sizeToFit];
+
+ [self placeChild:box on:parent];
+
+ [group release];
+ [box release];
+
# endif // !USE_IPHONE
}
} 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);
}
- (void)traverseChildren:(NSXMLNode *)node on:(NSView *)parent
{
NSArray *children = [node children];
- int i, count = [children count];
+ NSUInteger i, count = [children count];
for (i = 0; i < count; i++) {
NSXMLNode *child = [children objectAtIndex:i];
[self makeControl:child on:parent];
}
/*
-Bad:
- parent: 420 x 541 @ 0 0
- text: 380 x 100 @ 20 22 miny=-501
+ Bad:
+ parent: 420 x 541 @ 0 0
+ text: 380 x 100 @ 20 22 miny=-501
-Good:
- parent: 420 x 541 @ 0 0
- text: 380 x 100 @ 20 50 miny=-501
-*/
+ Good:
+ parent: 420 x 541 @ 0 0
+ text: 380 x 100 @ 20 50 miny=-501
+ */
// #### WTF2: See "WTF" above. If the text field is off the screen,
// move it up. We need this on 10.6 but not on 10.5. Auugh.
[cancel setAction:@selector(cancelAction:)];
[reset setAction:@selector(resetAction:)];
+ [bbox release];
+
return pbox;
}
#endif // !USE_IPHONE
}
saver_name = [self parseXScreenSaverTag: node];
+ saver_name = [saver_name stringByReplacingOccurrencesOfString:@" "
+ withString:@""];
[saver_name retain];
# ifndef USE_IPHONE
fix_contentview_size (panel);
NSView *root = wrap_with_buttons (parent, panel);
- [userDefaultsController setAppliesImmediately:NO];
+ [userDefaultsController setAppliesImmediately:NO];
+ [globalDefaultsController setAppliesImmediately:NO];
[panel setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
[parent setMinSize:rect.size];
[parent setContentView:root];
+
+ [panel release];
+ [root release];
# else // USE_IPHONE
- (CGFloat)pickerView:(UIPickerView *)pv
rowHeightForComponent:(NSInteger)column
{
- return [NSFont systemFontSize] * 1.5; // #### WHAT
+ return FONT_SIZE;
}
- (CGFloat)pickerView:(UIPickerView *)pv
style: UIBarButtonItemStyleBordered
target:self
action:@selector(resetAction:)]];
+ NSString *s = saver_name;
+ if ([self view].frame.size.width > 320)
+ s = [s stringByAppendingString: @" Settings"];
+ [self navigationItem].title = s;
}
titleForHeaderInSection:(NSInteger)section
{
// Titles above each vertically-stacked white box.
- if (section == 0)
- return [saver_name stringByAppendingString:@" Settings"];
+// if (section == 0)
+// return [saver_name stringByAppendingString:@" Settings"];
return nil;
}
[t sizeToFit];
r = t.frame;
h = r.size.height + LINE_SPACING * 3;
+# ifdef USE_HTML_LABELS
+ } else if ([ctl isKindOfClass:[HTMLLabel class]]) {
+
+ HTMLLabel *t = (HTMLLabel *) ctl;
+ CGRect r = t.frame;
+ r.size.width = [tv frame].size.width;
+ r.size.width -= LEFT_MARGIN * 2;
+ [t setFrame:r];
+ [t sizeToFit];
+ r = t.frame;
+ h = r.size.height + LINE_SPACING * 3;
+
+# endif // USE_HTML_LABELS
} else {
CGFloat h2 = [ctl frame].size.height;
h2 += LINE_SPACING * 2;
{
UITableView *tv = (UITableView *) [self view];
NSMutableArray *a = [NSMutableArray arrayWithCapacity:20];
- int rows = [self numberOfSectionsInTableView:tv];
+ NSInteger rows = [self numberOfSectionsInTableView:tv];
for (int i = 0; i < rows; i++) {
- int cols = [self tableView:tv numberOfRowsInSection:i];
+ NSInteger cols = [self tableView:tv numberOfRowsInSection:i];
for (int j = 0; j < cols; j++) {
NSUInteger ip[2];
ip[0] = i;
NSArray *item = [[b items] objectAtIndex: [b index]];
NSString *pref_key = [item objectAtIndex:1];
NSObject *pref_val = [item objectAtIndex:2];
- NSObject *current = [userDefaultsController objectForKey:pref_key];
+
+ NSObject *current = [[self controllerForKey:pref_key] objectForKey:pref_key];
// Convert them both to strings and compare those, so that
// we don't get screwed by int 1 versus string "1".
case 2:
{
// With 2 elements, the first of the pair must be a label.
- ctl = [set objectAtIndex: 0];
- NSAssert ([ctl isKindOfClass:[UILabel class]], @"unhandled type");
- cell.textLabel.text = [(UILabel *) ctl text];
+ UILabel *label = (UILabel *) [set objectAtIndex: 0];
+ NSAssert ([label isKindOfClass:[UILabel class]], @"unhandled type");
ctl = [set objectAtIndex: 1];
r = [ctl frame];
}
r.origin.y = (p.size.height - r.size.height) / 2;
[ctl setFrame:r];
+
+ // Make a box.
+ NSView *box = [[UIView alloc] initWithFrame:p];
+ [box addSubview: ctl];
+
+ // cell.textLabel.text = [(UILabel *) ctl text];
+ r = [label frame];
+ r.origin.x = LEFT_MARGIN;
+ r.origin.y = 0;
+ r.size.width = [ctl frame].origin.x - r.origin.x;
+ r.size.height = p.size.height;
+ [label setFrame:r];
+ [label setFont:[NSFont boldSystemFontOfSize: FONT_SIZE]];
+ label.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
+ box. autoresizingMask = UIViewAutoresizingFlexibleWidth;
+ [box addSubview: label];
+
+ ctl = box;
}
break;
case 3:
// Top label goes above, flush center/top.
if (top) {
- // [top setFont:[[cell textLabel] font]]; // 0 point?
r.size = [[top text] sizeWithFont:[top font]
constrainedToSize:
CGSizeMake (p.size.width - LEFT_MARGIN*2,
top.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin|
UIViewAutoresizingFlexibleRightMargin);
# else
- cell.textLabel.text = [top text];
+ r = [top frame];
+ r.origin.x = LEFT_MARGIN;
+ r.origin.y = 0;
+ r.size.width = [mid frame].origin.x - r.origin.x;
+ r.size.height = p.size.height;
+ [top setFrame:r];
+ top.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
+ [ctl addSubview: top];
# endif
}
[ctl addSubview: left];
# endif // USE_PICKER_VIEW
}
+ if ([ctl isKindOfClass:[UILabel class]]) {
+ // Make label full height to allow text to line-wrap if necessary.
+ r = [ctl frame];
+ r.origin.y = p.origin.y;
+ r.size.height = p.size.height;
+ [ctl setFrame:r];
+ }
+
[cell.contentView addSubview: ctl];
return cell;
controls on itself that are hooked up to the appropriate preferences.
The default size of the view is just big enough to hold them all.
*/
-- (id)initWithXMLFile: (NSString *) xml_file
- options: (const XrmOptionDescRec *) _opts
- controller: (NSUserDefaultsController *) _prefs
- defaults: (NSDictionary *) _defs
+- (id)initWithXML: (NSData *) xml_data
+ options: (const XrmOptionDescRec *) _opts
+ controller: (NSUserDefaultsController *) _prefs
+ globalController: (NSUserDefaultsController *) _globalPrefs
+ defaults: (NSDictionary *) _defs
{
# ifndef USE_IPHONE
self = [super init];
// instance variables
opts = _opts;
defaultOptions = _defs;
- userDefaultsController = _prefs;
- [userDefaultsController retain];
-
- NSURL *furl = [NSURL fileURLWithPath:xml_file];
-
- if (!furl) {
- NSAssert1 (0, @"can't URLify \"%@\"", xml_file);
- return nil;
- }
-
-#if 0 // -- the old way
- NSError *err = nil;
- NSXMLDocument *xmlDoc = [[NSXMLDocument alloc]
- initWithContentsOfURL:furl
- options:(NSXMLNodePreserveWhitespace |
- NSXMLNodePreserveCDATA)
- error:&err];
- if (!xmlDoc || err) {
- if (err)
- NSAssert2 (0, @"XML Error: %@: %@",
- xml_file, [err localizedDescription]);
- return nil;
- }
-
- traverse_tree (prefs, self, opts, [xmlDoc rootElement]);
-#endif /* 0 */
+ userDefaultsController = [_prefs retain];
+ globalDefaultsController = [_globalPrefs retain];
+ NSXMLParser *xmlDoc = [[NSXMLParser alloc] initWithData:xml_data];
- NSXMLParser *xmlDoc = [[NSXMLParser alloc] initWithContentsOfURL:furl];
if (!xmlDoc) {
- NSAssert1 (0, @"XML Error: %@", xml_file);
+ NSAssert1 (0, @"XML Error: %@",
+ [[NSString alloc] initWithData:xml_data
+ encoding:NSUTF8StringEncoding]);
return nil;
}
[xmlDoc setDelegate:self];
if (! [xmlDoc parse]) {
NSError *err = [xmlDoc parserError];
- NSAssert2 (0, @"XML Error: %@: %@", xml_file, err);
+ NSAssert2 (0, @"XML Error: %@: %@",
+ [[NSString alloc] initWithData:xml_data
+ encoding:NSUTF8StringEncoding],
+ err);
return nil;
}
{
[saver_name release];
[userDefaultsController release];
+ [globalDefaultsController release];
# ifdef USE_IPHONE
[controls release];
[pref_keys release];