+#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.
+ [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:@"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 *wiki = "http://en.wikipedia.org/wiki/";
+ const char *math = "http://mathworld.wolfram.com/";
+ if (!strncmp (wiki, url, strlen(wiki))) {
+ char *anchor = (char *) malloc (strlen(url) * 3 + 10);
+ strcpy (anchor, "Wikipedia: \"");
+ const char *in = url + strlen(wiki);
+ 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 (math, url, strlen(math))) {
+ char *anchor = (char *) malloc (strlen(url) * 3 + 10);
+ strcpy (anchor, "MathWorld: \"");
+ const char *start = url + strlen(wiki);
+ 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
+
+ int 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];
+ 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)...
+ //
+ NSRange 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);