Files
MASPreferences/Framework/MASPreferencesWindowController.m
2016-11-05 14:00:52 +08:00

333 lines
12 KiB
Objective-C

#import "MASPreferencesWindowController.h"
#import "MASPreferencesViewController.h"
NSString *const kMASPreferencesWindowControllerDidChangeViewNotification = @"MASPreferencesWindowControllerDidChangeViewNotification";
static NSString *const kMASPreferencesFrameTopLeftKey = @"MASPreferences Frame Top Left";
static NSString *const kMASPreferencesSelectedViewKey = @"MASPreferences Selected Identifier View";
static NSString * PreferencesKeyForViewBounds (NSString *identifier)
{
return [NSString stringWithFormat:@"MASPreferences %@ Frame", identifier];
}
@interface MASPreferencesWindowController () // Private
- (NSViewController <MASPreferencesViewController> *)viewControllerForIdentifier:(NSString *)identifier;
@property (readonly) NSArray *toolbarItemIdentifiers;
@property (nonatomic, retain) NSViewController <MASPreferencesViewController> *selectedViewController;
@end
#pragma mark -
@implementation MASPreferencesWindowController
@synthesize viewControllers = _viewControllers;
@synthesize selectedViewController = _selectedViewController;
@synthesize title = _title;
@synthesize toolbar = _toolbar;
#pragma mark -
- (instancetype)initWithViewControllers:(NSArray *)viewControllers
{
return [self initWithViewControllers:viewControllers title:nil];
}
- (instancetype)initWithViewControllers:(NSArray *)viewControllers title:(NSString *)title
{
NSParameterAssert(viewControllers.count > 0);
NSString *nibPath = [[NSBundle bundleForClass:MASPreferencesWindowController.class] pathForResource:@"MASPreferencesWindow" ofType:@"nib"];
if ((self = [super initWithWindowNibPath:nibPath owner:self]))
{
_viewControllers = [viewControllers mutableCopy];
_minimumViewRects = [[NSMutableDictionary alloc] init];
_title = [title copy];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[self window] setDelegate:nil];
for (NSToolbarItem *item in [self.toolbar items]) {
item.target = nil;
item.action = nil;
}
self.toolbar.delegate = nil;
}
- (void)addViewController:(NSViewController <MASPreferencesViewController> *)viewController
{
NSParameterAssert(viewController);
[_viewControllers addObject: viewController];
[_toolbar insertItemWithItemIdentifier: [viewController identifier] atIndex: ([_viewControllers count] - 1)];
[_toolbar validateVisibleItems];
}
#pragma mark -
- (void)windowDidLoad
{
if ([self.title length] > 0)
[[self window] setTitle:self.title];
if ([self.viewControllers count])
self.selectedViewController = [self viewControllerForIdentifier:[[NSUserDefaults standardUserDefaults] stringForKey:kMASPreferencesSelectedViewKey]] ?: [self firstViewController];
NSString *origin = [[NSUserDefaults standardUserDefaults] stringForKey:kMASPreferencesFrameTopLeftKey];
if (origin)
[self.window setFrameTopLeftPoint:NSPointFromString(origin)];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidMove:) name:NSWindowDidMoveNotification object:self.window];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidResize:) name:NSWindowDidResizeNotification object:self.window];
}
- (NSViewController <MASPreferencesViewController> *)firstViewController {
for (id viewController in self.viewControllers)
if ([viewController isKindOfClass:[NSViewController class]])
return viewController;
return nil;
}
#pragma mark -
#pragma mark NSWindowDelegate
- (BOOL)windowShouldClose:(id __unused)sender
{
return !self.selectedViewController || [self.selectedViewController commitEditing];
}
- (void)windowDidMove:(NSNotification* __unused)aNotification
{
[[NSUserDefaults standardUserDefaults] setObject:NSStringFromPoint(NSMakePoint(NSMinX([self.window frame]), NSMaxY([self.window frame]))) forKey:kMASPreferencesFrameTopLeftKey];
}
- (void)windowDidResize:(NSNotification* __unused)aNotification
{
NSViewController <MASPreferencesViewController> *viewController = self.selectedViewController;
if (viewController)
[[NSUserDefaults standardUserDefaults] setObject:NSStringFromRect([viewController.view bounds]) forKey:PreferencesKeyForViewBounds(viewController.identifier)];
}
#pragma mark -
#pragma mark Accessors
- (NSArray *)toolbarItemIdentifiers
{
NSMutableArray *identifiers = [NSMutableArray arrayWithCapacity:_viewControllers.count];
for (id viewController in _viewControllers)
if (viewController == [NSNull null])
[identifiers addObject:NSToolbarFlexibleSpaceItemIdentifier];
else
[identifiers addObject:[viewController identifier]];
return identifiers;
}
#pragma mark -
- (NSUInteger)indexOfSelectedController
{
NSUInteger index = [self.toolbarItemIdentifiers indexOfObject:self.selectedViewController.identifier];
return index;
}
#pragma mark -
#pragma mark NSToolbarDelegate
- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar * __unused)toolbar
{
NSArray *identifiers = self.toolbarItemIdentifiers;
return identifiers;
}
- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar * __unused)toolbar
{
NSArray *identifiers = self.toolbarItemIdentifiers;
return identifiers;
}
- (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar * __unused)toolbar
{
NSArray *identifiers = self.toolbarItemIdentifiers;
return identifiers;
}
- (NSToolbarItem *)toolbar:(NSToolbar * __unused)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL __unused)flag
{
NSToolbarItem *toolbarItem = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
NSArray *identifiers = self.toolbarItemIdentifiers;
NSUInteger controllerIndex = [identifiers indexOfObject:itemIdentifier];
if (controllerIndex != NSNotFound)
{
id <MASPreferencesViewController> controller = [_viewControllers objectAtIndex:controllerIndex];
toolbarItem.image = controller.toolbarItemImage;
toolbarItem.label = controller.toolbarItemLabel;
toolbarItem.target = self;
toolbarItem.action = @selector(toolbarItemDidClick:);
}
return toolbarItem;
}
#pragma mark -
#pragma mark Private methods
- (NSViewController <MASPreferencesViewController> *)viewControllerForIdentifier:(NSString *)identifier
{
for (id viewController in self.viewControllers) {
if (viewController == [NSNull null]) continue;
if ([[viewController identifier] isEqualToString:identifier])
return viewController;
}
return nil;
}
#pragma mark -
- (void)setSelectedViewController:(NSViewController <MASPreferencesViewController> *)controller
{
if (_selectedViewController == controller)
return;
if (_selectedViewController)
{
// Check if we can commit changes for old controller
if (![_selectedViewController commitEditing])
{
[[self.window toolbar] setSelectedItemIdentifier:_selectedViewController.identifier];
return;
}
[self.window setContentView:[[NSView alloc] init]];
[_selectedViewController setNextResponder:nil];
// With 10.10 and later AppKit will invoke viewDidDisappear so we need to prevent it from being called twice.
if (![NSViewController instancesRespondToSelector:@selector(viewDidDisappear)])
if ([_selectedViewController respondsToSelector:@selector(viewDidDisappear)])
[_selectedViewController viewDidDisappear];
_selectedViewController = nil;
}
if (!controller)
return;
// Retrieve the new window tile from the controller view
if ([self.title length] == 0)
{
NSString *label = controller.toolbarItemLabel;
self.window.title = label;
}
[[self.window toolbar] setSelectedItemIdentifier:controller.identifier];
// Record new selected controller in user defaults
[[NSUserDefaults standardUserDefaults] setObject:controller.identifier forKey:kMASPreferencesSelectedViewKey];
NSView *controllerView = controller.view;
// Retrieve current and minimum frame size for the view
NSString *oldViewRectString = [[NSUserDefaults standardUserDefaults] stringForKey:PreferencesKeyForViewBounds(controller.identifier)];
NSString *minViewRectString = [_minimumViewRects objectForKey:controller.identifier];
if (!minViewRectString)
[_minimumViewRects setObject:NSStringFromRect(controllerView.bounds) forKey:controller.identifier];
BOOL sizableWidth = 0;
BOOL sizableHeight = 0;
NSRect oldViewRect = oldViewRectString ? NSRectFromString(oldViewRectString) : controllerView.bounds;
NSRect minViewRect = minViewRectString ? NSRectFromString(minViewRectString) : controllerView.bounds;
oldViewRect.size.width = NSWidth(oldViewRect) < NSWidth(minViewRect) || !sizableWidth ? NSWidth(minViewRect) : NSWidth(oldViewRect);
oldViewRect.size.height = NSHeight(oldViewRect) < NSHeight(minViewRect) || !sizableHeight ? NSHeight(minViewRect) : NSHeight(oldViewRect);
[controllerView setFrame:oldViewRect];
// Calculate new window size and position
NSRect oldFrame = [self.window frame];
NSRect newFrame = [self.window frameRectForContentRect:oldViewRect];
newFrame = NSOffsetRect(newFrame, NSMinX(oldFrame), NSMaxY(oldFrame) - NSMaxY(newFrame));
// Setup min/max sizes and show/hide resize indicator
[self.window setContentMinSize:minViewRect.size];
[self.window setContentMaxSize:NSMakeSize(sizableWidth ? CGFLOAT_MAX : NSWidth(oldViewRect), sizableHeight ? CGFLOAT_MAX : NSHeight(oldViewRect))];
[self.window setShowsResizeIndicator:sizableWidth || sizableHeight];
[[self.window standardWindowButton:NSWindowZoomButton] setEnabled:sizableWidth || sizableHeight];
[self.window setFrame:newFrame display:YES animate:[self.window isVisible]];
_selectedViewController = controller;
// In OSX 10.10, setContentView below calls viewWillAppear. We still want to call viewWillAppear on < 10.10,
// so the check below avoids calling viewWillAppear twice on 10.10.
// See https://github.com/shpakovski/MASPreferences/issues/32 for more info.
if (![NSViewController instancesRespondToSelector:@selector(viewWillAppear)])
if ([controller respondsToSelector:@selector(viewWillAppear)])
[controller viewWillAppear];
[self.window setContentView:controllerView];
[self.window recalculateKeyViewLoop];
if ([self.window firstResponder] == self.window) {
if ([controller respondsToSelector:@selector(initialKeyView)])
[self.window makeFirstResponder:[controller initialKeyView]];
else
[self.window selectKeyViewFollowingView:controllerView];
}
// Insert view controller into responder chain on 10.9 and earlier
if (controllerView.nextResponder != controller) {
controller.nextResponder = controllerView.nextResponder;
controllerView.nextResponder = controller;
}
[[NSNotificationCenter defaultCenter] postNotificationName:kMASPreferencesWindowControllerDidChangeViewNotification object:self];
}
- (void)toolbarItemDidClick:(id)sender
{
if ([sender respondsToSelector:@selector(itemIdentifier)])
self.selectedViewController = [self viewControllerForIdentifier:[sender itemIdentifier]];
}
#pragma mark -
#pragma mark Public methods
- (void)selectControllerAtIndex:(NSUInteger)controllerIndex
{
if (NSLocationInRange(controllerIndex, NSMakeRange(0, _viewControllers.count)))
self.selectedViewController = [self.viewControllers objectAtIndex:controllerIndex];
}
- (void)selectControllerWithIdentifier:(NSString *)identifier
{
NSParameterAssert(identifier.length > 0);
self.selectedViewController = [self viewControllerForIdentifier:identifier];
}
#pragma mark -
#pragma mark Actions
- (IBAction)goNextTab:(id __unused)sender
{
NSUInteger selectedIndex = self.indexOfSelectedController;
NSUInteger numberOfControllers = [_viewControllers count];
do { selectedIndex = (selectedIndex + 1) % numberOfControllers; }
while ([_viewControllers objectAtIndex:selectedIndex] == [NSNull null]);
[self selectControllerAtIndex:selectedIndex];
}
- (IBAction)goPreviousTab:(id __unused)sender
{
NSUInteger selectedIndex = self.indexOfSelectedController;
NSUInteger numberOfControllers = [_viewControllers count];
do { selectedIndex = (selectedIndex + numberOfControllers - 1) % numberOfControllers; }
while ([_viewControllers objectAtIndex:selectedIndex] == [NSNull null]);
[self selectControllerAtIndex:selectedIndex];
}
@end