2 // DBPrefsWindowController.m
5 #import "DBPrefsWindowController.h"
8 static DBPrefsWindowController *_sharedPrefsWindowController = nil;
11 @implementation DBPrefsWindowController
17 #pragma mark Class Methods
20 + (DBPrefsWindowController *)sharedPrefsWindowController
22 if (!_sharedPrefsWindowController) {
23 _sharedPrefsWindowController = [[self alloc] initWithWindowNibName:[self nibName]];
25 return _sharedPrefsWindowController;
32 // Subclasses can override this to use a nib with a different name.
34 return @"Preferences";
41 #pragma mark Setup & Teardown
44 - (id)initWithWindow:(NSWindow *)window
45 // -initWithWindow: is the designated initializer for NSWindowController.
47 self = [super initWithWindow:nil];
49 // Set up an array and some dictionaries to keep track
50 // of the views we'll be displaying.
51 toolbarIdentifiers = [[NSMutableArray alloc] init];
52 toolbarViews = [[NSMutableDictionary alloc] init];
53 toolbarItems = [[NSMutableDictionary alloc] init];
55 // Set up an NSViewAnimation to animate the transitions.
56 viewAnimation = [[NSViewAnimation alloc] init];
57 [viewAnimation setAnimationBlockingMode:NSAnimationNonblocking];
58 [viewAnimation setAnimationCurve:NSAnimationEaseInOut];
59 [viewAnimation setDelegate:self];
61 [self setCrossFade:YES];
62 [self setShiftSlowsAnimation:YES];
66 (void)window; // To prevent compiler warnings.
74 // Create a new window to display the preference views.
75 // If the developer attached a window to this controller
76 // in Interface Builder, it gets replaced with this one.
77 NSWindow *window = [[[NSWindow alloc] initWithContentRect:NSMakeRect(0,0,1000,1000)
78 styleMask:(NSTitledWindowMask |
79 NSClosableWindowMask |
80 NSMiniaturizableWindowMask)
81 backing:NSBackingStoreBuffered
82 defer:YES] autorelease];
83 [self setWindow:window];
84 contentSubview = [[[NSView alloc] initWithFrame:[[[self window] contentView] frame]] autorelease];
85 [contentSubview setAutoresizingMask:(NSViewMinYMargin | NSViewWidthSizable)];
86 [[[self window] contentView] addSubview:contentSubview];
87 [[self window] setShowsToolbarButton:NO];
94 [toolbarIdentifiers release];
95 [toolbarViews release];
96 [toolbarItems release];
97 [viewAnimation release];
105 #pragma mark Configuration
110 // Subclasses must override this method to add items to the
111 // toolbar by calling -addView:label: or -addView:label:image:.
117 - (void)addView:(NSView *)view label:(NSString *)label
121 image:[NSImage imageNamed:label]];
127 - (void)addView:(NSView *)view label:(NSString *)label image:(NSImage *)image
129 NSAssert (view != nil,
130 @"Attempted to add a nil view when calling -addView:label:image:.");
132 NSString *identifier = [[label copy] autorelease];
134 [toolbarIdentifiers addObject:identifier];
135 [toolbarViews setObject:view forKey:identifier];
137 NSToolbarItem *item = [[[NSToolbarItem alloc] initWithItemIdentifier:identifier] autorelease];
138 [item setLabel:label];
139 [item setImage:image];
140 [item setTarget:self];
141 [item setAction:@selector(toggleActivePreferenceView:)];
143 [toolbarItems setObject:item forKey:identifier];
150 #pragma mark Accessor Methods
161 - (void)setCrossFade:(BOOL)fade
169 - (BOOL)shiftSlowsAnimation
171 return _shiftSlowsAnimation;
177 - (void)setShiftSlowsAnimation:(BOOL)slows
179 _shiftSlowsAnimation = slows;
186 #pragma mark Overriding Methods
189 - (IBAction)showWindow:(id)sender
191 // This forces the resources in the nib to load.
194 // Clear the last setup and get a fresh one.
195 [toolbarIdentifiers removeAllObjects];
196 [toolbarViews removeAllObjects];
197 [toolbarItems removeAllObjects];
200 NSAssert (([toolbarIdentifiers count] > 0),
201 @"No items were added to the toolbar in -setupToolbar.");
203 if ([[self window] toolbar] == nil) {
204 NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"DBPreferencesToolbar"];
205 [toolbar setAllowsUserCustomization:NO];
206 [toolbar setAutosavesConfiguration:NO];
207 [toolbar setSizeMode:NSToolbarSizeModeDefault];
208 [toolbar setDisplayMode:NSToolbarDisplayModeIconAndLabel];
209 [toolbar setDelegate:self];
210 [[self window] setToolbar:toolbar];
214 NSString *firstIdentifier = [toolbarIdentifiers objectAtIndex:0];
215 [[[self window] toolbar] setSelectedItemIdentifier:firstIdentifier];
216 [self displayViewForIdentifier:firstIdentifier animate:NO];
218 [[self window] center];
220 [super showWindow:sender];
230 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar
232 return toolbarIdentifiers;
240 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar
242 return toolbarIdentifiers;
250 - (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar
252 return toolbarIdentifiers;
259 - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)identifier willBeInsertedIntoToolbar:(BOOL)willBeInserted
261 return [toolbarItems objectForKey:identifier];
263 (void)willBeInserted;
269 - (void)toggleActivePreferenceView:(NSToolbarItem *)toolbarItem
271 [self displayViewForIdentifier:[toolbarItem itemIdentifier] animate:YES];
277 - (void)displayViewForIdentifier:(NSString *)identifier animate:(BOOL)animate
279 // Find the view we want to display.
280 NSView *newView = [toolbarViews objectForKey:identifier];
282 // See if there are any visible views.
283 NSView *oldView = nil;
284 if ([[contentSubview subviews] count] > 0) {
285 // Get a list of all of the views in the window. Usually at this
286 // point there is just one visible view. But if the last fade
287 // hasn't finished, we need to get rid of it now before we move on.
288 NSEnumerator *subviewsEnum = [[contentSubview subviews] reverseObjectEnumerator];
290 // The first one (last one added) is our visible view.
291 oldView = [subviewsEnum nextObject];
293 // Remove any others.
294 NSView *reallyOldView = nil;
295 while ((reallyOldView = [subviewsEnum nextObject]) != nil) {
296 [reallyOldView removeFromSuperviewWithoutNeedingDisplay];
300 if (![newView isEqualTo:oldView]) {
301 NSRect frame = [newView bounds];
302 frame.origin.y = NSHeight([contentSubview frame]) - NSHeight([newView bounds]);
303 [newView setFrame:frame];
304 [contentSubview addSubview:newView];
305 [[self window] setInitialFirstResponder:newView];
307 if (animate && [self crossFade])
308 [self crossFadeView:oldView withView:newView];
310 [oldView removeFromSuperviewWithoutNeedingDisplay];
311 [newView setHidden:NO];
312 [[self window] setFrame:[self frameForView:newView] display:YES animate:animate];
315 [[self window] setTitle:[[toolbarItems objectForKey:identifier] label]];
323 #pragma mark Cross-Fading Methods
326 - (void)crossFadeView:(NSView *)oldView withView:(NSView *)newView
328 [viewAnimation stopAnimation];
330 if ([self shiftSlowsAnimation] && [[[self window] currentEvent] modifierFlags] & NSShiftKeyMask)
331 [viewAnimation setDuration:1.25];
333 [viewAnimation setDuration:0.25];
335 NSDictionary *fadeOutDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
336 oldView, NSViewAnimationTargetKey,
337 NSViewAnimationFadeOutEffect, NSViewAnimationEffectKey,
340 NSDictionary *fadeInDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
341 newView, NSViewAnimationTargetKey,
342 NSViewAnimationFadeInEffect, NSViewAnimationEffectKey,
345 NSDictionary *resizeDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
346 [self window], NSViewAnimationTargetKey,
347 [NSValue valueWithRect:[[self window] frame]], NSViewAnimationStartFrameKey,
348 [NSValue valueWithRect:[self frameForView:newView]], NSViewAnimationEndFrameKey,
351 NSArray *animationArray = [NSArray arrayWithObjects:
357 [viewAnimation setViewAnimations:animationArray];
358 [viewAnimation startAnimation];
364 - (void)animationDidEnd:(NSAnimation *)animation
368 // Get a list of all of the views in the window. Hopefully
369 // at this point there are two. One is visible and one is hidden.
370 NSEnumerator *subviewsEnum = [[contentSubview subviews] reverseObjectEnumerator];
372 // This is our visible view. Just get past it.
373 subview = [subviewsEnum nextObject];
375 // Remove everything else. There should be just one, but
376 // if the user does a lot of fast clicking, we might have
377 // more than one to remove.
378 while ((subview = [subviewsEnum nextObject]) != nil) {
379 [subview removeFromSuperviewWithoutNeedingDisplay];
382 // This is a work-around that prevents the first
383 // toolbar icon from becoming highlighted.
384 [[self window] makeFirstResponder:nil];
392 - (NSRect)frameForView:(NSView *)view
393 // Calculate the window size for the new view.
395 NSRect windowFrame = [[self window] frame];
396 NSRect contentRect = [[self window] contentRectForFrameRect:windowFrame];
397 float windowTitleAndToolbarHeight = NSHeight(windowFrame) - NSHeight(contentRect);
399 windowFrame.size.height = NSHeight([view frame]) + windowTitleAndToolbarHeight;
400 windowFrame.size.width = NSWidth([view frame]);
401 windowFrame.origin.y = NSMaxY([[self window] frame]) - NSHeight(windowFrame);