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 NSPanel *window = [[[NSPanel alloc] initWithContentRect:NSMakeRect(0,0,1000,1000)
78 styleMask:(NSTitledWindowMask |
80 backing:NSBackingStoreBuffered
81 defer:YES] autorelease];
82 [window setHidesOnDeactivate:NO];
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;
185 - (NSString *)currentPaneIdentifier
186 // Subclasses can override this to persist the current preference pane.
188 return currentPaneIdentifier;
194 - (void)setCurrentPaneIdentifier:(NSString *)identifier
195 // Subclasses can override this to persist the current preference pane.
197 currentPaneIdentifier = identifier;
204 #pragma mark Overriding Methods
207 - (IBAction)showWindow:(id)sender
209 // This forces the resources in the nib to load.
212 // Clear the last setup and get a fresh one.
213 [toolbarIdentifiers removeAllObjects];
214 [toolbarViews removeAllObjects];
215 [toolbarItems removeAllObjects];
218 NSAssert (([toolbarIdentifiers count] > 0),
219 @"No items were added to the toolbar in -setupToolbar.");
221 if ([[self window] toolbar] == nil) {
222 NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"DBPreferencesToolbar"];
223 [toolbar setAllowsUserCustomization:NO];
224 [toolbar setAutosavesConfiguration:NO];
225 [toolbar setSizeMode:NSToolbarSizeModeDefault];
226 [toolbar setDisplayMode:NSToolbarDisplayModeIconAndLabel];
227 [toolbar setDelegate:self];
228 [[self window] setToolbar:toolbar];
232 if ([toolbarItems objectForKey:[self currentPaneIdentifier]] == nil) {
233 [self setCurrentPaneIdentifier:[toolbarIdentifiers objectAtIndex:0]];
235 [[[self window] toolbar]
236 setSelectedItemIdentifier:[self currentPaneIdentifier]];
237 [self displayViewForIdentifier:[self currentPaneIdentifier] animate:NO];
239 [[self window] center];
241 [super showWindow:sender];
251 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar
253 return toolbarIdentifiers;
261 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar
263 return toolbarIdentifiers;
271 - (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar
273 return toolbarIdentifiers;
280 - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)identifier willBeInsertedIntoToolbar:(BOOL)willBeInserted
282 return [toolbarItems objectForKey:identifier];
284 (void)willBeInserted;
290 - (void)toggleActivePreferenceView:(NSToolbarItem *)toolbarItem
292 [self displayViewForIdentifier:[toolbarItem itemIdentifier] animate:YES];
293 [self setCurrentPaneIdentifier:[toolbarItem itemIdentifier]];
299 - (void)displayViewForIdentifier:(NSString *)identifier animate:(BOOL)animate
301 // Find the view we want to display.
302 NSView *newView = [toolbarViews objectForKey:identifier];
304 // See if there are any visible views.
305 NSView *oldView = nil;
306 if ([[contentSubview subviews] count] > 0) {
307 // Get a list of all of the views in the window. Usually at this
308 // point there is just one visible view. But if the last fade
309 // hasn't finished, we need to get rid of it now before we move on.
310 NSEnumerator *subviewsEnum = [[contentSubview subviews] reverseObjectEnumerator];
312 // The first one (last one added) is our visible view.
313 oldView = [subviewsEnum nextObject];
315 // Remove any others.
316 NSView *reallyOldView = nil;
317 while ((reallyOldView = [subviewsEnum nextObject]) != nil) {
318 [reallyOldView removeFromSuperviewWithoutNeedingDisplay];
322 if (![newView isEqualTo:oldView]) {
323 NSRect frame = [newView bounds];
324 frame.origin.y = NSHeight([contentSubview frame]) - NSHeight([newView bounds]);
325 [newView setFrame:frame];
326 [contentSubview addSubview:newView];
327 [[self window] setInitialFirstResponder:newView];
329 if (animate && [self crossFade])
330 [self crossFadeView:oldView withView:newView];
332 [oldView removeFromSuperviewWithoutNeedingDisplay];
333 [newView setHidden:NO];
334 [[self window] setFrame:[self frameForView:newView] display:YES animate:animate];
337 [[self window] setTitle:[[toolbarItems objectForKey:identifier] label]];
345 #pragma mark Cross-Fading Methods
348 - (void)crossFadeView:(NSView *)oldView withView:(NSView *)newView
350 [viewAnimation stopAnimation];
352 if ([self shiftSlowsAnimation] && [[[self window] currentEvent] modifierFlags] & NSShiftKeyMask)
353 [viewAnimation setDuration:1.25];
355 [viewAnimation setDuration:0.25];
357 NSDictionary *fadeOutDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
358 oldView, NSViewAnimationTargetKey,
359 NSViewAnimationFadeOutEffect, NSViewAnimationEffectKey,
362 NSDictionary *fadeInDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
363 newView, NSViewAnimationTargetKey,
364 NSViewAnimationFadeInEffect, NSViewAnimationEffectKey,
367 NSDictionary *resizeDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
368 [self window], NSViewAnimationTargetKey,
369 [NSValue valueWithRect:[[self window] frame]], NSViewAnimationStartFrameKey,
370 [NSValue valueWithRect:[self frameForView:newView]], NSViewAnimationEndFrameKey,
373 NSArray *animationArray = [NSArray arrayWithObjects:
379 [viewAnimation setViewAnimations:animationArray];
380 [viewAnimation startAnimation];
386 - (void)animationDidEnd:(NSAnimation *)animation
390 // Get a list of all of the views in the window. Hopefully
391 // at this point there are two. One is visible and one is hidden.
392 NSEnumerator *subviewsEnum = [[contentSubview subviews] reverseObjectEnumerator];
394 // This is our visible view. Just get past it.
395 subview = [subviewsEnum nextObject];
397 // Remove everything else. There should be just one, but
398 // if the user does a lot of fast clicking, we might have
399 // more than one to remove.
400 while ((subview = [subviewsEnum nextObject]) != nil) {
401 [subview removeFromSuperviewWithoutNeedingDisplay];
404 // This is a work-around that prevents the first
405 // toolbar icon from becoming highlighted.
406 [[self window] makeFirstResponder:nil];
414 - (NSRect)frameForView:(NSView *)view
415 // Calculate the window size for the new view.
417 NSRect windowFrame = [[self window] frame];
418 NSRect contentRect = [[self window] contentRectForFrameRect:windowFrame];
419 float windowTitleAndToolbarHeight = NSHeight(windowFrame) - NSHeight(contentRect);
421 windowFrame.size.height = NSHeight([view frame]) + windowTitleAndToolbarHeight;
422 windowFrame.size.width = NSWidth([view frame]);
423 windowFrame.origin.y = NSMaxY([[self window] frame]) - NSHeight(windowFrame);