-/* xscreensaver, Copyright (c) 2006 Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright (c) 2006-2009 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
NSAssert(cmdline_switch, @"cmdline switch is null");
if (! [cmdline_switch getCString:buf maxLength:sizeof(buf)
encoding:NSUTF8StringEncoding]) {
- NSAssert1(0, @"unable to convert %@\n", cmdline_switch);
+ NSAssert1(0, @"unable to convert %@", cmdline_switch);
abort();
}
char *s = strpbrk(buf, " \t\r\n");
if (opts[0].argKind == XrmoptionNoArg) {
if (tail && *tail)
- NSAssert1 (0, @"expected no args to switch: \"%@\"\n",
+ NSAssert1 (0, @"expected no args to switch: \"%@\"",
cmdline_switch);
ret = opts[0].value;
} else {
if (!tail || !*tail)
- NSAssert1 (0, @"expected args to switch: \"%@\"\n",
+ NSAssert1 (0, @"expected args to switch: \"%@\"",
cmdline_switch);
ret = tail;
}
opts++;
}
- NSAssert1 (0, @"\"%@\" not present in options\n", cmdline_switch);
+ NSAssert1 (0, @"\"%@\" not present in options", cmdline_switch);
abort();
}
s = [s stringByPaddingToLength:18 withString:@" " startingAtIndex:0];
s = [NSString stringWithFormat:@"%@ = \"%@\"", s, def];
s = [s stringByPaddingToLength:28 withString:@" " startingAtIndex:0];
- NSLog (@"%@ %@/%@\n", s, [def class], [control class]);
+ NSLog (@"%@ %@/%@", s, [def class], [control class]);
# endif
}
NSString *old = [dict objectForKey:key];
if (! old) {
- NSAssert2 (0, @"unknown attribute \"%@\" in \"%@\"\n", key, [node name]);
+ NSAssert2 (0, @"unknown attribute \"%@\" in \"%@\"", key, [node name]);
} else if ([old length] != 0) {
- NSAssert2 (0, @"duplicate %@: \"%@\", \"%@\"\n", old, val);
+ NSAssert2 (0, @"duplicate %@: \"%@\", \"%@\"", old, val);
} else {
[dict setValue:val forKey:key];
}
NSString *arg_unset = [dict objectForKey:@"arg-unset"];
if (!label) {
- NSAssert1 (0, @"no _label in %@\n", [node name]);
+ NSAssert1 (0, @"no _label in %@", [node name]);
return;
}
if (!arg_set && !arg_unset) {
- NSAssert1 (0, @"neither arg-set nor arg-unset provided in \"%@\"\n",
+ 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 \"%@\"\n",
+ NSAssert1 (0, @"only one of arg-set and arg-unset may be used in \"%@\"",
label);
}
//
if (arg_set && ([arg_set hasPrefix:@"-no-"] ||
[arg_set hasPrefix:@"--no-"]))
- NSLog (@"arg-set should not be a \"no\" option in \"%@\": %@\n",
+ 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 \"%@\": %@\n",
+ NSLog(@"arg-unset should be a \"no\" option in \"%@\": %@",
label, arg_unset);
NSRect rect;
NSString *arg = [dict objectForKey:@"arg"];
if (!label && !no_label_p) {
- NSAssert1 (0, @"no _label in %@\n", [node name]);
+ NSAssert1 (0, @"no _label in %@", [node name]);
return;
}
- NSAssert1 (arg, @"no arg in %@\n", label);
+ NSAssert1 (arg, @"no arg in %@", label);
NSRect rect;
rect.origin.x = rect.origin.y = 0;
NSString *arg = [dict objectForKey:@"arg"];
if (!label && !no_label_p) {
- NSAssert1 (0, @"no _label in %@\n", [node name]);
+ NSAssert1 (0, @"no _label in %@", [node name]);
return;
}
- NSAssert1 (arg, @"no arg in %@\n", label);
+ NSAssert1 (arg, @"no arg in %@", label);
NSRect rect;
rect.origin.x = rect.origin.y = 0;
types:nil];
if (result == NSOKButton) {
NSArray *files = [panel filenames];
- NSString *file = ([files count] > 0 ? [files objectAtIndex:0] : @"");
+ file = ([files count] > 0 ? [files objectAtIndex:0] : @"");
file = [file stringByAbbreviatingWithTildeInPath];
[txt setStringValue:file];
NSString *def = [dict objectForKey:@"default"];
NSString *cvt = [dict objectForKey:@"convert"];
- NSAssert1 (arg, @"no arg in %@\n", label);
- NSAssert1 (type, @"no type in %@\n", label);
+ NSAssert1 (arg, @"no arg in %@", label);
+ NSAssert1 (type, @"no type in %@", label);
if (! low) {
- NSAssert1 (0, @"no low in %@\n", [node name]);
+ NSAssert1 (0, @"no low in %@", [node name]);
return;
}
if (! high) {
- NSAssert1 (0, @"no high in %@\n", [node name]);
+ NSAssert1 (0, @"no high in %@", [node name]);
return;
}
if (! def) {
- NSAssert1 (0, @"no default in %@\n", [node name]);
+ NSAssert1 (0, @"no default in %@", [node name]);
return;
}
if (cvt && ![cvt isEqualToString:@"invert"]) {
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"]) {
NSRect rect;
rect.origin.x = rect.origin.y = 0;
rect.size.width = 150;
- rect.size.height = 20;
+ rect.size.height = 23; // apparent min height for slider with ticks...
NSSlider *slider;
if (cvt)
slider = [[InvertedSlider alloc] initWithFrame:rect];
[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;
+
+ // 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
+
+ // #### 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?
+
if (label) {
NSTextField *lab = make_label (label);
place_child (parent, lab, NO);
} else if ([type isEqualToString:@"spinbutton"]) {
if (! label) {
- NSAssert1 (0, @"no _label in spinbutton %@\n", [node name]);
+ NSAssert1 (0, @"no _label in spinbutton %@", [node name]);
return;
}
NSAssert1 (!low_label,
- @"low-label not allowed in spinbutton \"%@\"\n", [node name]);
+ @"low-label not allowed in spinbutton \"%@\"", [node name]);
NSAssert1 (!high_label,
- @"high-label not allowed in spinbutton \"%@\"\n", [node name]);
- NSAssert1 (!cvt, @"convert not allowed in spinbutton \"%@\"\n",
+ @"high-label not allowed in spinbutton \"%@\"", [node name]);
+ NSAssert1 (!cvt, @"convert not allowed in spinbutton \"%@\"",
[node name]);
NSRect rect;
[step sizeToFit];
place_child (parent, step, YES);
rect = [step frame];
- rect.size.height = [txt frame].size.height;
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]];
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];
+
+
bind_switch_to_preferences (prefs, step, arg, opts);
bind_switch_to_preferences (prefs, txt, arg, opts);
[txt release];
} else {
- NSAssert2 (0, @"unknown type \"%@\" in \"%@\"\n", type, label);
+ NSAssert2 (0, @"unknown type \"%@\" in \"%@\"", type, label);
}
}
int i, count = [children count];
if (count <= 0) {
- NSAssert1 (0, @"no menu items in \"%@\"\n", [node name]);
+ NSAssert1 (0, @"no menu items in \"%@\"", [node name]);
return;
}
rect.origin.x = rect.origin.y = 0;
rect.size.width = 10;
rect.size.height = 10;
+
+ // #### "Build and Analyze" says that all of our widgets leak, because it
+ // seems to not realize that place_child -> addSubview retains them.
+ // Not sure what to do to make these warnings go away.
+
NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:rect
pullsDown:NO];
NSString *arg_set = [dict2 objectForKey:@"arg-set"];
if (!label) {
- NSAssert1 (0, @"no _label in %@\n", [child name]);
+ NSAssert1 (0, @"no _label in %@", [child name]);
return;
}
NSAssert1 (this_val, @"this_val null for %@", arg_set);
if (menu_key && ![menu_key isEqualToString:this_key])
NSAssert3 (0,
- @"multiple resources in menu: \"%@\" vs \"%@\" = \"%@\"\n",
+ @"multiple resources in menu: \"%@\" vs \"%@\" = \"%@\"",
menu_key, this_key, this_val);
if (this_key)
menu_key = this_key;
} else {
// no arg-set -- only one menu item can be missing that.
- NSAssert1 (!def_item, @"no arg-set in \"%@\"\n", label);
+ NSAssert1 (!def_item, @"no arg-set in \"%@\"", label);
def_item = item;
}
}
+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);
+ }
+}
+
+
/* Converts any http: URLs in the given text field to clickable links.
*/
static void
NSString *nsurl = [text substringWithRange:r2];
const char *url = [nsurl UTF8String];
- // Construct the RTF corresponding to <A HREF="url">url</A>
+ // If this is a Wikipedia URL, make the linked text be prettier.
+ //
+ char *anchor = anchorize(url);
+
+ // 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) * 2) + 10);
- sprintf (rtf, fmt, url, url);
+ char *rtf = malloc (strlen (fmt) + strlen(url) + strlen(anchor) + 10);
+ sprintf (rtf, fmt, url, anchor);
+ free (anchor);
NSData *rtfdata = [NSData dataWithBytesNoCopy:rtf length:strlen(rtf)];
// Insert the RTF into the NSText.
[nstext replaceCharactersInRange:r2 withRTF:rtfdata];
+
+ int L2 = [text length]; // might have changed
+ start.location -= (L - L2);
+ L = L2;
}
}
-/* Makes the first word of the text be bold.
+/* Makes the text up to the first comma be bold.
*/
static void
boldify (NSText *nstext)
{
NSString *text = [nstext string];
- NSRange r = [text rangeOfCharacterFromSet:
- [NSCharacterSet whitespaceCharacterSet]];
- r.length = r.location;
+ NSRange r = [text rangeOfString:@"," options:0];
+ r.length = r.location+1;
r.location = 0;
NSFont *font = [nstext font];
{
/*
Display Text:
- (x) Computer Name and Time
+ (x) Computer name and time
( ) Text [__________________________]
( ) Text file [_________________] [Choose]
( ) URL [__________________________]
[matrix setAllowsEmptySelection:NO];
NSArrayController *cnames = [[NSArrayController alloc] initWithContent:nil];
- [cnames addObject:@"Computer Name and Time"];
+ [cnames addObject:@"Computer name and time"];
[cnames addObject:@"Text"];
[cnames addObject:@"File"];
[cnames addObject:@"URL"];
make_text_field (prefs, opts, rgroup, node2, YES);
[node2 release];
- rect = [last_child(rgroup) frame];
+// rect = [last_child(rgroup) frame];
/* // trying to make the text fields be enabled only when the checkbox is on..
control = last_child (rgroup);
make_file_selector (prefs, opts, rgroup, node2, NO, YES);
[node2 release];
- rect = [last_child(rgroup) frame];
+// rect = [last_child(rgroup) frame];
// <string id="textURL" _label="" arg-set="text-url %"/>
node2 = [[NSXMLElement alloc] initWithName:@"string"];
make_text_field (prefs, opts, rgroup, node2, YES);
[node2 release];
- rect = [last_child(rgroup) frame];
+// rect = [last_child(rgroup) frame];
layout_group (rgroup, NO);
NSView *parent, NSXMLNode *node)
{
/*
- [x] Grab Desktop Images
- [ ] Choose Random Image:
+ [x] Grab desktop images
+ [ ] Choose random image:
[__________________________] [Choose]
- <boolean id="grabDesktopImages" _label="Grab Desktop Images"
+ <boolean id="grabDesktopImages" _label="Grab desktop images"
arg-unset="-no-grab-desktop"/>
- <boolean id="chooseRandomImages" _label="Grab Desktop Images"
+ <boolean id="chooseRandomImages" _label="Grab desktop images"
arg-unset="-choose-random-images"/>
<file id="imageDirectory" _label="" arg-set="-image-directory %"/>
*/
[node2 setAttributesAsDictionary:
[NSDictionary dictionaryWithObjectsAndKeys:
@"grabDesktopImages", @"id",
- @"Grab Desktop Images", @"_label",
+ @"Grab desktop images", @"_label",
@"-no-grab-desktop", @"arg-unset",
nil]];
make_checkbox (prefs, opts, parent, node2);
[node2 setAttributesAsDictionary:
[NSDictionary dictionaryWithObjectsAndKeys:
@"chooseRandomImages", @"id",
- @"Choose Random Images", @"_label",
+ @"Choose random images", @"_label",
@"-choose-random-images", @"arg-set",
nil]];
make_checkbox (prefs, opts, parent, node2);
[node2 setAttributesAsDictionary:
[NSDictionary dictionaryWithObjectsAndKeys:
@"imageDirectory", @"id",
- @"Images Directory:", @"_label",
+ @"Images directory:", @"_label",
@"-image-directory %", @"arg",
nil]];
make_file_selector (prefs, opts, parent, node2, YES, NO);
make_image_controls (prefs, opts, parent, node);
} else {
- NSAssert1 (0, @"unknown tag: %@\n", name);
+ NSAssert1 (0, @"unknown tag: %@", name);
}
}
NSString *label = [dict objectForKey:@"_label"];
if (!label) {
- NSAssert1 (0, @"no _label in %@\n", [node name]);
+ NSAssert1 (0, @"no _label in %@", [node name]);
return;
}
if (!name) {
- NSAssert1 (0, @"no name in \"%@\"\n", label);
+ NSAssert1 (0, @"no name in \"%@\"", label);
return;
}
NSRect f;
NSArray *kids = [parent subviews];
int nkids = [kids count];
- NSView *text; // the NSText at the bottom of the window
- NSView *last; // the last child before the NSText
+ NSView *text = 0; // the NSText at the bottom of the window
double maxx = 0, miny = 0;
int i;
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;
- last = kid;
// 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 < 350) maxx = 350; // leave room for the NSText paragraph...
+ 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,
miny = f.origin.y - LINE_SPACING;
[text setFrame:f];
- // Stop second-guessing us on sizing now. Size is now locked.
+ // 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;
+// float yoff = f.size.height;
f.size.width = maxx + LEFT_MARGIN;
f.size.height = -(miny - LEFT_MARGIN*2);
- yoff = f.size.height - yoff;
+// yoff = f.size.height - yoff;
[parent setFrame:f];
// NSLog(@"max: %3.0f x %3.0f @ %3.0f %3.0f",
// 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.
*/
rect.origin.y += rect.size.height;
NSBox *pbox = [[NSBox alloc] initWithFrame:rect];
[pbox setTitlePosition:NSNoTitle];
- [pbox setBorderType:NSNoBorder];
+ [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];
NSURL *furl = [NSURL fileURLWithPath:xml_file];
if (!furl) {
- NSAssert1 (0, @"can't URLify \"%@\"\n", xml_file);
+ NSAssert1 (0, @"can't URLify \"%@\"", xml_file);
return nil;
}
options:(NSXMLNodePreserveWhitespace |
NSXMLNodePreserveCDATA)
error:&err];
-/* clean up?
- if (!xmlDoc) {
- xmlDoc = [[NSXMLDocument alloc] initWithContentsOfURL:furl
- options:NSXMLDocumentTidyXML
- error:&err];
- }
-*/
if (!xmlDoc || err) {
if (err)
- NSAssert2 (0, @"XML Error: %@: %@\n",
+ NSAssert2 (0, @"XML Error: %@: %@",
xml_file, [err localizedDescription]);
return nil;
}
traverse_tree (prefs, self, opts, [xmlDoc rootElement]);
+ [xmlDoc release];
return self;
}