4ea79aaae06933b72fefdbfb29e111c41abee197
[xscreensaver] / OSX / SaverTester.m
1 /* xscreensaver, Copyright (c) 2006-2008 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 is just a test harness, really -- it makes a window with an
13    XScreenSaverGLView in it and a Preferences button, because I don't want
14    to have to debug these programs by installing them as screen savers
15    first!
16  */
17
18 #import "SaverTester.h"
19 #import "XScreenSaverGLView.h"
20
21 @implementation SaverTester
22
23 - (ScreenSaverView *) makeSaverView: (NSString *) module
24 {
25   NSString *dir = [[[NSBundle mainBundle] bundlePath]
26                    stringByDeletingLastPathComponent];
27   NSString *name = [module stringByAppendingPathExtension:@"saver"];
28   NSString *path = [dir stringByAppendingPathComponent:name];
29   NSBundle *bundleToLoad = [NSBundle bundleWithPath:path];
30   Class new_class = [bundleToLoad principalClass];
31   NSAssert1 (new_class, @"unable to load \"%@\"", path);
32
33   NSRect rect;
34   rect.origin.x = rect.origin.y = 0;
35   rect.size.width = 320;
36   rect.size.height = 240;
37
38   id instance = [[new_class alloc] initWithFrame:rect isPreview:YES];
39   NSAssert1 (instance, @"unable to instantiate %@", new_class);
40   return (ScreenSaverView *) instance;
41 }
42
43
44 static ScreenSaverView *
45 find_saverView_child (NSView *v)
46 {
47   NSArray *kids = [v subviews];
48   int nkids = [kids count];
49   int i;
50   for (i = 0; i < nkids; i++) {
51     NSObject *kid = [kids objectAtIndex:i];
52     if ([kid isKindOfClass:[ScreenSaverView class]]) {
53       return (ScreenSaverView *) kid;
54     } else {
55       ScreenSaverView *sv = find_saverView_child ((NSView *) kid);
56       if (sv) return sv;
57     }
58   }
59   return 0;
60 }
61
62
63 static ScreenSaverView *
64 find_saverView (NSView *v)
65 {
66   while (1) {
67     NSView *p = [v superview];
68     if (p) v = p;
69     else break;
70   }
71   return find_saverView_child (v);
72 }
73
74
75 - (void) openPreferences: (NSObject *) button
76 {
77   ScreenSaverView *sv = find_saverView ((NSView *) button);
78   NSAssert (sv, @"no saver view");
79   NSWindow *prefs = [sv configureSheet];
80
81   [NSApp beginSheet:prefs
82      modalForWindow:[sv window]
83       modalDelegate:self
84      didEndSelector:@selector(preferencesClosed:returnCode:contextInfo:)
85         contextInfo:nil];
86   int code = [NSApp runModalForWindow:prefs];
87   
88   /* Restart the animation if the "OK" button was hit, but not if "Cancel".
89      We have to restart *both* animations, because the xlockmore-style
90      ones will blow up if one re-inits but the other doesn't.
91    */
92   if (code != NSCancelButton) {
93     [sv stopAnimation];
94     [sv startAnimation];
95   }
96 }
97
98 - (void) preferencesClosed: (NSWindow *) sheet
99                 returnCode: (int) returnCode
100                contextInfo: (void  *) contextInfo
101 {
102   [NSApp stopModalWithCode:returnCode];
103 }
104
105
106 - (void)selectedSaverDidChange:(NSDictionary *)change
107 {
108   NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
109   NSString *module = [prefs stringForKey:@"selectedSaverName"];
110   int i;
111
112   if (! module) return;
113
114   for (i = 0; i < [windows count]; i++) {
115     NSView *cv = [[windows objectAtIndex:i] contentView];
116     ScreenSaverView *old_view = find_saverView (cv);
117     NSView *sup = [old_view superview];
118
119     [old_view stopAnimation];
120     [old_view removeFromSuperview];
121
122     ScreenSaverView *new_view = [self makeSaverView:module];
123     [new_view setFrame: [old_view frame]];
124     [sup addSubview: new_view];
125     [new_view setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
126     [new_view startAnimation];
127   }
128
129   NSUserDefaultsController *ctl =
130     [NSUserDefaultsController sharedUserDefaultsController];
131   [ctl save:self];
132 }
133
134
135 - (NSArray *) listSaverBundleNames
136 {
137   NSString *dir = [[[NSBundle mainBundle] bundlePath]
138                    stringByDeletingLastPathComponent];
139   NSString *longest = 0;
140   NSArray *matches = 0;
141   NSArray *exts = [NSArray arrayWithObjects:@"saver", nil];
142   unsigned int n = [dir completePathIntoString: &longest
143                                  caseSensitive: NO
144                               matchesIntoArray: &matches
145                                    filterTypes: exts];
146   if (n <= 0) {
147     NSLog (@"no .saver bundles found in \"%@/\"!", dir);
148     exit (1);
149   }
150
151   int i;
152   NSMutableArray *result = [NSMutableArray arrayWithCapacity: n+1];
153   for (i = 0; i < n; i++)
154     [result addObject: [[[matches objectAtIndex: i] lastPathComponent]
155                          stringByDeletingPathExtension]];
156   return result;
157 }
158
159
160 - (NSPopUpButton *) makeMenu
161 {
162   NSRect rect;
163   rect.origin.x = rect.origin.y = 0;
164   rect.size.width = 10;
165   rect.size.height = 10;
166   NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:rect
167                                                     pullsDown:NO];
168   int i;
169   float max_width = 0;
170   for (i = 0; i < [saverNames count]; i++) {
171     NSString *name = [saverNames objectAtIndex:i];
172     [popup addItemWithTitle:name];
173     [[popup itemWithTitle:name] setRepresentedObject:name];
174     [popup sizeToFit];
175     NSRect r = [popup frame];
176     if (r.size.width > max_width) max_width = r.size.width;
177   }
178
179   // Bind the menu to preferences, and trigger a callback when an item
180   // is selected.
181   //
182   NSString *key = @"values.selectedSaverName";
183   NSUserDefaultsController *prefs =
184     [NSUserDefaultsController sharedUserDefaultsController];
185   [prefs addObserver:self
186          forKeyPath:key
187             options:nil
188             context:@selector(selectedSaverDidChange:)];
189   [popup   bind:@"selectedObject"
190        toObject:prefs
191     withKeyPath:key
192         options:nil];
193   [prefs setAppliesImmediately:YES];
194
195   NSRect r = [popup frame];
196   r.size.width = max_width;
197   [popup setFrame:r];
198   return popup;
199 }
200
201
202 /* This is called when the "selectedSaverName" pref changes, e.g.,
203    when a menu selection is made.
204  */
205 - (void)observeValueForKeyPath:(NSString *)keyPath
206                       ofObject:(id)object
207                         change:(NSDictionary *)change
208                        context:(void *)context
209 {
210   SEL dispatchSelector = (SEL)context;
211   if (dispatchSelector != NULL) {
212     [self performSelector:dispatchSelector withObject:change];
213   } else {
214     [super observeValueForKeyPath:keyPath
215                          ofObject:object
216                            change:change
217                           context:context];
218   }
219 }
220
221
222
223 - (NSWindow *) makeWindow
224 {
225   NSRect rect;
226   static int count = 0;
227
228   // make a "Preferences" button
229   //
230   rect.origin.x = rect.origin.y = 0;
231   rect.size.width = rect.size.height = 10;
232   NSButton *pb = [[NSButton alloc] initWithFrame:rect];
233   [pb setTitle:@"Preferences"];
234   [pb setBezelStyle:NSRoundedBezelStyle];
235   [pb sizeToFit];
236
237
238   NSRect sv_rect = rect;
239   sv_rect.size.width = 320;
240   sv_rect.size.height = 240;
241   ScreenSaverView *sv = [[ScreenSaverView alloc]  // dummy placeholder
242                           initWithFrame:sv_rect
243                           isPreview:YES];
244
245   rect.origin.x = ([sv frame].size.width -
246                    [pb frame].size.width) / 2;
247   [pb setFrameOrigin:rect.origin];
248   
249   // grab the click
250   //
251   [pb setTarget:self];
252   [pb setAction:@selector(openPreferences:)];
253
254   // Make a saver selection menu
255   //
256   NSPopUpButton *menu = [self makeMenu];
257   rect.origin.x = 2;
258   rect.origin.y = 2;
259   [menu setFrameOrigin:rect.origin];
260
261   // make a box to wrap the saverView
262   //
263   rect = [sv frame];
264   rect.origin.x = 0;
265   rect.origin.y = [pb frame].origin.y + [pb frame].size.height;
266   NSBox *gbox = [[NSBox alloc] initWithFrame:rect];
267   rect.size.width = rect.size.height = 10;
268   [gbox setContentViewMargins:rect.size];
269   [gbox setTitlePosition:NSNoTitle];
270   [gbox addSubview:sv];
271   [gbox sizeToFit];
272
273   // make a box to wrap the other two boxes
274   //
275   rect.origin.x = rect.origin.y = 0;
276   rect.size.width  = [gbox frame].size.width;
277   rect.size.height = [gbox frame].size.height + [gbox frame].origin.y;
278   NSBox *pbox = [[NSBox alloc] initWithFrame:rect];
279   [pbox setTitlePosition:NSNoTitle];
280   [pbox setBorderType:NSNoBorder];
281   [pbox addSubview:gbox];
282   [pbox addSubview:menu];
283   [pbox addSubview:pb];
284   [pbox sizeToFit];
285
286   // and make a window to hold that.
287   //
288   NSScreen *screen = [NSScreen mainScreen];
289   rect = [pbox frame];
290   rect.origin.x = ([screen frame].size.width  - rect.size.width)  / 2;
291   rect.origin.y = ([screen frame].size.height - rect.size.height) / 2;
292   
293   rect.origin.x += rect.size.width * (count ? 0.55 : -0.55);
294   
295   NSWindow *window = [[NSWindow alloc]
296                       initWithContentRect:rect
297                                 styleMask:(NSTitledWindowMask |
298                                            NSClosableWindowMask |
299                                            NSMiniaturizableWindowMask |
300                                            NSResizableWindowMask)
301                                   backing:NSBackingStoreBuffered
302                                     defer:YES
303                                    screen:screen];
304   [window setTitle:@"XScreenSaver"];
305   [window setMinSize:[window frameRectForContentRect:rect].size];
306
307   [[window contentView] addSubview:pbox];
308
309   [sv   setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
310   [pb   setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin];
311   [menu setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin];
312   [gbox setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
313   [pbox setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
314     
315   char buf[100];
316   sprintf (buf, "SaverDebugWindow%d", count);
317   [window setFrameAutosaveName:
318             [NSString stringWithCString:buf encoding:NSUTF8StringEncoding]];
319   [window setFrameUsingName:[window frameAutosaveName]];
320   
321   [window makeKeyAndOrderFront:window];
322   
323   [sv startAnimation]; // this is the dummy saver
324
325   count++;
326
327   return window;
328 }
329
330
331 - (void)applicationDidFinishLaunching: (NSNotification *) notif
332 {
333   saverNames = [[self listSaverBundleNames] retain];
334
335   int i, n = 2;
336   NSMutableArray *w = [[NSMutableArray arrayWithCapacity: n+1] retain];
337   windows = w;
338   for (i = 0; i < n; i++)
339     [w addObject: [self makeWindow]];
340
341   [self selectedSaverDidChange:nil];
342 }
343
344
345 /* When the window closes, exit (even if prefs still open.)
346 */
347 - (BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication *) n
348 {
349   return YES;
350 }
351
352 @end