From http://www.jwz.org/xscreensaver/xscreensaver-5.15.tar.gz
[xscreensaver] / OSX / PrefsReader.m
1 /* xscreensaver, Copyright (c) 2006-2011 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 /* This implements the substrate of the xscreensaver preferences code:
13    It does this by writing defaults to, and reading values from, the
14    NSUserDefaultsController (and ScreenSaverDefaults/NSUserDefaults)
15    and thereby reading the preferences that may have been edited by
16    the UI (XScreenSaverConfigSheet).
17  */
18
19 #import <ScreenSaver/ScreenSaverDefaults.h>
20 #import "PrefsReader.h"
21 #import "screenhackI.h"
22
23 @implementation PrefsReader
24
25 /* Converts an array of "key:value" strings to an NSDictionary.
26  */
27 - (NSDictionary *) defaultsToDict: (const char * const *) defs
28 {
29   NSDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:20];
30   while (*defs) {
31     char *line = strdup (*defs);
32     char *key, *val;
33     key = line;
34     while (*key == '.' || *key == '*' || *key == ' ' || *key == '\t')
35       key++;
36     val = key;
37     while (*val && *val != ':')
38       val++;
39     if (*val != ':') abort();
40     *val++ = 0;
41     while (*val == ' ' || *val == '\t')
42       val++;
43
44     int L = strlen(val);
45     while (L > 0 && (val[L-1] == ' ' || val[L-1] == '\t'))
46       val[--L] = 0;
47
48     // When storing into preferences, look at the default string and
49     // decide whether it's a boolean, int, float, or string, and store
50     // an object of the appropriate type in the prefs.
51     //
52     NSString *nskey = [NSString stringWithCString:key
53                                          encoding:NSUTF8StringEncoding];
54     NSObject *nsval;
55     int dd;
56     double ff;
57     char cc;
58     if (!strcasecmp (val, "true") || !strcasecmp (val, "yes"))
59       nsval = [NSNumber numberWithBool:YES];
60     else if (!strcasecmp (val, "false") || !strcasecmp (val, "no"))
61       nsval = [NSNumber numberWithBool:NO];
62     else if (1 == sscanf (val, " %d %c", &dd, &cc))
63       nsval = [NSNumber numberWithInt:dd];
64     else if (1 == sscanf (val, " %lf %c", &ff, &cc))
65       nsval = [NSNumber numberWithDouble:ff];
66     else
67       nsval = [NSString stringWithCString:val encoding:NSUTF8StringEncoding];
68       
69 //    NSLog (@"default: \"%@\" = \"%@\" [%@]", nskey, nsval, [nsval class]);
70     [dict setValue:nsval forKey:nskey];
71     free (line);
72     defs++;
73   }
74   return dict;
75 }
76
77
78 /* Initialize the Cocoa preferences database:
79    - sets the default preferences values from the 'defaults' array;
80    - binds 'self' to each preference as an observer;
81    - ensures that nothing is mentioned in 'options' and not in 'defaults';
82    - ensures that nothing is mentioned in 'defaults' and not in 'options'.
83  */
84 - (void) registerXrmKeys: (const XrmOptionDescRec *) opts
85                 defaults: (const char * const *) defs
86 {
87   // Store the contents of 'defaults' into the real preferences database.
88   NSDictionary *defsdict = [self defaultsToDict:defs];
89   [userDefaults registerDefaults:defsdict];
90
91   userDefaultsController = 
92     [[NSUserDefaultsController alloc] initWithDefaults:userDefaults
93                                       initialValues:defsdict];
94
95   NSDictionary *optsdict = [NSMutableDictionary dictionaryWithCapacity:20];
96
97   while (opts[0].option) {
98     //const char *option   = opts->option;
99     const char *resource = opts->specifier;
100     
101     while (*resource == '.' || *resource == '*')
102       resource++;
103     NSString *nsresource = [NSString stringWithCString:resource
104                                               encoding:NSUTF8StringEncoding];
105     
106     // make sure there's no resource mentioned in options and not defaults.
107     if (![defsdict objectForKey:nsresource]) {
108       if (! (!strcmp(resource, "font")        ||    // don't warn about these
109              !strcmp(resource, "textLiteral") ||
110              !strcmp(resource, "textFile")    ||
111              !strcmp(resource, "textURL")     ||
112              !strcmp(resource, "textProgram") ||
113              !strcmp(resource, "imageDirectory")))
114         NSLog (@"warning: \"%s\" is in options but not defaults", resource);
115     }
116     [optsdict setValue:nsresource forKey:nsresource];
117     
118     opts++;
119   }
120
121 #if 0
122   // make sure there's no resource mentioned in defaults and not options.
123   NSEnumerator *enumerator = [defsdict keyEnumerator];
124   NSString *key;
125   while ((key = [enumerator nextObject])) {
126     if (! [optsdict objectForKey:key])
127       if (! ([key isEqualToString:@"foreground"] || // don't warn about these
128              [key isEqualToString:@"background"] ||
129              [key isEqualToString:@"Background"] ||
130              [key isEqualToString:@"geometry"] ||
131              [key isEqualToString:@"font"] ||
132              [key isEqualToString:@"dontClearRoot"] ||
133
134              // fps.c settings
135              [key isEqualToString:@"fpsSolid"] ||
136              [key isEqualToString:@"fpsTop"] ||
137              [key isEqualToString:@"titleFont"] ||
138
139              // analogtv.c settings
140              [key isEqualToString:@"TVBrightness"] ||
141              [key isEqualToString:@"TVColor"] ||
142              [key isEqualToString:@"TVContrast"] ||
143              [key isEqualToString:@"TVTint"]
144              ))
145       NSLog (@"warning: \"%@\" is in defaults but not options", key);
146   }
147 #endif /* 0 */
148
149 }
150
151 - (NSUserDefaultsController *) userDefaultsController
152 {
153   NSAssert(userDefaultsController, @"userDefaultsController uninitialized");
154   return userDefaultsController;
155 }
156
157
158 - (NSObject *) getObjectResource: (const char *) name
159 {
160   while (1) {
161     NSString *key = [NSString stringWithCString:name
162                                        encoding:NSUTF8StringEncoding];
163     NSObject *obj = [userDefaults objectForKey:key];
164     if (obj)
165       return obj;
166
167     // If key is "foo.bar.baz", check "foo.bar.baz", "bar.baz", and "baz".
168     //
169     const char *dot = strchr (name, '.');
170     if (dot && dot[1])
171       name = dot + 1;
172     else
173       return nil;
174   }
175 }
176
177
178 - (char *) getStringResource: (const char *) name
179 {
180   NSObject *o = [self getObjectResource:name];
181   //NSLog(@"%s = %@",name,o);
182   if (o == nil) {
183     if (! (!strcmp(name, "eraseMode") || // erase.c
184            // xlockmore.c reads all of these whether used or not...
185            !strcmp(name, "right3d") ||
186            !strcmp(name, "left3d") ||
187            !strcmp(name, "both3d") ||
188            !strcmp(name, "none3d") ||
189            !strcmp(name, "font") ||
190            !strcmp(name, "labelFont") ||  // grabclient.c
191            !strcmp(name, "titleFont") ||
192            !strcmp(name, "fpsFont") ||    // fps.c
193            !strcmp(name, "foreground") || // fps.c
194            !strcmp(name, "background")
195            ))
196       NSLog(@"warning: no preference \"%s\" [string]", name);
197     return NULL;
198   }
199   if (! [o isKindOfClass:[NSString class]]) {
200     NSLog(@"asked for %s as a string, but it is a %@", name, [o class]);
201     o = [(NSNumber *) o stringValue];
202   }
203
204   NSString *os = (NSString *) o;
205   char *result = strdup ([os cStringUsingEncoding:NSUTF8StringEncoding]);
206
207   // Kludge: if the string is surrounded with single-quotes, remove them.
208   // This happens when the .xml file says things like arg="-foo 'bar baz'"
209   if (result[0] == '\'' && result[strlen(result)-1] == '\'') {
210     result[strlen(result)-1] = 0;
211     strcpy (result, result+1);
212   }
213
214   // Kludge: assume that any string that begins with "~" and has a "/"
215   // anywhere in it should be expanded as if it is a pathname.
216   if (result[0] == '~' && strchr (result, '/')) {
217     os = [NSString stringWithCString:result encoding:NSUTF8StringEncoding];
218     free (result);
219     result = strdup ([[os stringByExpandingTildeInPath]
220                        cStringUsingEncoding:NSUTF8StringEncoding]);
221   }
222
223   return result;
224 }
225
226
227 - (double) getFloatResource: (const char *) name
228 {
229   NSObject *o = [self getObjectResource:name];
230   if (o == nil) {
231     // xlockmore.c reads all of these whether used or not...
232     if (! (!strcmp(name, "cycles") ||
233            !strcmp(name, "size") ||
234            !strcmp(name, "use3d") ||
235            !strcmp(name, "delta3d") ||
236            !strcmp(name, "wireframe") ||
237            !strcmp(name, "showFPS") ||
238            !strcmp(name, "fpsSolid") ||
239            !strcmp(name, "fpsTop") ||
240            !strcmp(name, "mono") ||
241            !strcmp(name, "count") ||
242            !strcmp(name, "ncolors") ||
243            !strcmp(name, "doFPS") ||      // fps.c
244            !strcmp(name, "eraseSeconds")  // erase.c
245            ))
246       NSLog(@"warning: no preference \"%s\" [float]", name);
247     return 0.0;
248   }
249   if ([o isKindOfClass:[NSString class]]) {
250     return [(NSString *) o doubleValue];
251   } else if ([o isKindOfClass:[NSNumber class]]) {
252     return [(NSNumber *) o doubleValue];
253   } else {
254     NSAssert2(0, @"%s = \"%@\" but should have been an NSNumber", name, o);
255     abort();
256   }
257 }
258
259
260 - (int) getIntegerResource: (const char *) name
261 {
262   // Sliders might store float values for integral resources; round them.
263   float v = [self getFloatResource:name];
264   int i = (int) (v + (v < 0 ? -0.5 : 0.5)); // ignore sign or -1 rounds to 0
265   // if (i != v) NSLog(@"%s: rounded %.3f to %d", name, v, i);
266   return i;
267 }
268
269
270 - (BOOL) getBooleanResource: (const char *) name
271 {
272   int n = [self getIntegerResource:name];
273   if (n == 0) return NO;
274   else if (n == 1) return YES;
275   else {
276     NSAssert2(0, @"%s = %d but should have been 0 or 1", name, n);
277     abort();
278   }
279 }
280
281
282 - (id) initWithName: (NSString *) name
283             xrmKeys: (const XrmOptionDescRec *) opts
284            defaults: (const char * const *) defs
285 {
286   self = [self init];
287   if (!self) return nil;
288
289   userDefaults = [ScreenSaverDefaults defaultsForModuleWithName:name];
290
291   [self registerXrmKeys:opts defaults:defs];
292   return self;
293 }
294
295 - (void) dealloc
296 {
297   [userDefaultsController release];
298   [super dealloc];
299 }
300
301 @end