project_files/HedgewarsMobile/Classes/MGSplitViewController/MGSplitViewController.m
author koda
Mon, 09 Apr 2012 23:20:42 +0200
changeset 6877 b899393c8450
parent 6659 a6030b32b222
child 6908 896ed2afcfb8
permissions -rwxr-xr-x
vob* variables need to be reset

//
//  MGSplitViewController.m
//  MGSplitView
//
//  Created by Matt Gemmell on 26/07/2010.
//  Copyright 2010 Instinctive Code.
//

#import "MGSplitViewController.h"
#import "MGSplitDividerView.h"
#import "MGSplitCornersView.h"

#define MG_DEFAULT_SPLIT_POSITION		320.0	// default width of master view in UISplitViewController.
#define MG_DEFAULT_SPLIT_WIDTH			1.0		// default width of split-gutter in UISplitViewController.
#define MG_DEFAULT_CORNER_RADIUS		5.0		// default corner-radius of overlapping split-inner corners on the master and detail views.
#define MG_DEFAULT_CORNER_COLOR			[UIColor blackColor]	// default color of intruding inner corners (and divider background).

#define MG_PANESPLITTER_CORNER_RADIUS	0.0		// corner-radius of split-inner corners for MGSplitViewDividerStylePaneSplitter style.
#define MG_PANESPLITTER_SPLIT_WIDTH		25.0	// width of split-gutter for MGSplitViewDividerStylePaneSplitter style.

#define MG_MIN_VIEW_WIDTH				200.0	// minimum width a view is allowed to become as a result of changing the splitPosition.

#define MG_ANIMATION_CHANGE_SPLIT_ORIENTATION	@"ChangeSplitOrientation"	// Animation ID for internal use.
#define MG_ANIMATION_CHANGE_SUBVIEWS_ORDER		@"ChangeSubviewsOrder"	// Animation ID for internal use.


@interface MGSplitViewController (MGPrivateMethods)

- (void)setup;
- (CGSize)splitViewSizeForOrientation:(UIInterfaceOrientation)theOrientation;
- (void)layoutSubviews;
- (void)layoutSubviewsWithAnimation:(BOOL)animate;
- (void)layoutSubviewsForInterfaceOrientation:(UIInterfaceOrientation)theOrientation withAnimation:(BOOL)animate;
- (BOOL)shouldShowMasterForInterfaceOrientation:(UIInterfaceOrientation)theOrientation;
- (BOOL)shouldShowMaster;
- (NSString *)nameOfInterfaceOrientation:(UIInterfaceOrientation)theOrientation;
- (void)reconfigureForMasterInPopover:(BOOL)inPopover;

@end


@implementation MGSplitViewController


#pragma mark -
#pragma mark Orientation helpers


- (NSString *)nameOfInterfaceOrientation:(UIInterfaceOrientation)theOrientation
{
	NSString *orientationName = nil;
	switch (theOrientation) {
		case UIInterfaceOrientationPortrait:
			orientationName = @"Portrait"; // Home button at bottom
			break;
		case UIInterfaceOrientationPortraitUpsideDown:
			orientationName = @"Portrait (Upside Down)"; // Home button at top
			break;
		case UIInterfaceOrientationLandscapeLeft:
			orientationName = @"Landscape (Left)"; // Home button on left
			break;
		case UIInterfaceOrientationLandscapeRight:
			orientationName = @"Landscape (Right)"; // Home button on right
			break;
		default:
			break;
	}
	
	return orientationName;
}


- (BOOL)isLandscape
{
	return UIInterfaceOrientationIsLandscape(self.interfaceOrientation);
}


- (BOOL)shouldShowMasterForInterfaceOrientation:(UIInterfaceOrientation)theOrientation
{
	// Returns YES if master view should be shown directly embedded in the splitview, instead of hidden in a popover.
	return ((UIInterfaceOrientationIsLandscape(theOrientation)) ? _showsMasterInLandscape : _showsMasterInPortrait);
}


- (BOOL)shouldShowMaster
{
	return [self shouldShowMasterForInterfaceOrientation:self.interfaceOrientation];
}


- (BOOL)isShowingMaster
{
	return [self shouldShowMaster] && self.masterViewController && self.masterViewController.view && ([self.masterViewController.view superview] == self.view);
}


#pragma mark -
#pragma mark Setup and Teardown


- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
	if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
		[self setup];
	}
	
	return self;
}


- (id)initWithCoder:(NSCoder *)aDecoder
{
	if ((self = [super initWithCoder:aDecoder])) {
		[self setup];
	}
	
	return self;
}


- (void)setup
{
	// Configure default behaviour.
	_viewControllers = [[NSMutableArray alloc] initWithObjects:[NSNull null], [NSNull null], nil];
	_splitWidth = MG_DEFAULT_SPLIT_WIDTH;
	_showsMasterInPortrait = NO;
	_showsMasterInLandscape = YES;
	_reconfigurePopup = NO;
	_vertical = YES;
	_masterBeforeDetail = YES;
	_splitPosition = MG_DEFAULT_SPLIT_POSITION;
	CGRect divRect = self.view.bounds;
	if ([self isVertical]) {
		divRect.origin.y = _splitPosition;
		divRect.size.height = _splitWidth;
	} else {
		divRect.origin.x = _splitPosition;
		divRect.size.width = _splitWidth;
	}
	_dividerView = [[MGSplitDividerView alloc] initWithFrame:divRect];
	_dividerView.splitViewController = self;
	_dividerView.backgroundColor = MG_DEFAULT_CORNER_COLOR;
	_dividerStyle = MGSplitViewDividerStyleThin;
}


- (void)dealloc
{
	_delegate = nil;
	[self.view.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
	[_viewControllers release];
	[_barButtonItem release];
	[_hiddenPopoverController release];
	[_dividerView release];
	[_cornerViews release];
	
	[super dealloc];
}


#pragma mark -
#pragma mark View management


- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return YES;
}


- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
	[self.masterViewController willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
	[self.detailViewController willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
}


- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
	[self.masterViewController didRotateFromInterfaceOrientation:fromInterfaceOrientation];
	[self.detailViewController didRotateFromInterfaceOrientation:fromInterfaceOrientation];
}


- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation 
										 duration:(NSTimeInterval)duration
{
	[self.masterViewController willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
	[self.detailViewController willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
	
	// Hide popover.
	if (_hiddenPopoverController && _hiddenPopoverController.popoverVisible) {
		[_hiddenPopoverController dismissPopoverAnimated:NO];
	}
	
	// Re-tile views.
	_reconfigurePopup = YES;
	[self layoutSubviewsForInterfaceOrientation:toInterfaceOrientation withAnimation:YES];
}


- (void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
	[self.masterViewController willAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
	[self.detailViewController willAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
}


- (void)didAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
	[self.masterViewController didAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation];
	[self.detailViewController didAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation];
}


- (void)willAnimateSecondHalfOfRotationFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation duration:(NSTimeInterval)duration
{
	[self.masterViewController willAnimateSecondHalfOfRotationFromInterfaceOrientation:fromInterfaceOrientation duration:duration];
	[self.detailViewController willAnimateSecondHalfOfRotationFromInterfaceOrientation:fromInterfaceOrientation duration:duration];
}


- (CGSize)splitViewSizeForOrientation:(UIInterfaceOrientation)theOrientation
{
	UIScreen *screen = [UIScreen mainScreen];
	CGRect fullScreenRect = screen.bounds; // always implicitly in Portrait orientation.
	CGRect appFrame = screen.applicationFrame;
	
	// Find status bar height by checking which dimension of the applicationFrame is narrower than screen bounds.
	// Little bit ugly looking, but it'll still work even if they change the status bar height in future.
	float statusBarHeight = MAX((fullScreenRect.size.width - appFrame.size.width), (fullScreenRect.size.height - appFrame.size.height));
	
	// Initially assume portrait orientation.
	float width = fullScreenRect.size.width;
	float height = fullScreenRect.size.height;
	
	// Correct for orientation.
	if (UIInterfaceOrientationIsLandscape(theOrientation)) {
		width = height;
		height = fullScreenRect.size.width;
	}
	
	// Account for status bar, which always subtracts from the height (since it's always at the top of the screen).
	height -= statusBarHeight;
	
	return CGSizeMake(width, height);
}


- (void)layoutSubviewsForInterfaceOrientation:(UIInterfaceOrientation)theOrientation withAnimation:(BOOL)animate
{
	if (_reconfigurePopup) {
		[self reconfigureForMasterInPopover:![self shouldShowMasterForInterfaceOrientation:theOrientation]];
	}
	
	// Layout the master, detail and divider views appropriately, adding/removing subviews as needed.
	// First obtain relevant geometry.
	CGSize fullSize = [self splitViewSizeForOrientation:theOrientation];
	float width = fullSize.width;
	float height = fullSize.height;
	
	if (NO) { // Just for debugging.
		NSLog(@"Target orientation is %@, dimensions will be %.0f x %.0f", 
			  [self nameOfInterfaceOrientation:theOrientation], width, height);
	}
	
	// Layout the master, divider and detail views.
	CGRect newFrame = CGRectMake(0, 0, width, height);
	UIViewController *controller;
	UIView *theView;
	BOOL shouldShowMaster = [self shouldShowMasterForInterfaceOrientation:theOrientation];
	BOOL masterFirst = [self isMasterBeforeDetail];
	if ([self isVertical]) {
		// Master on left, detail on right (or vice versa).
		CGRect masterRect, dividerRect, detailRect;
		if (masterFirst) {
			if (!shouldShowMaster) {
				// Move off-screen.
				newFrame.origin.x -= (_splitPosition + _splitWidth);
			}
			
			newFrame.size.width = _splitPosition;
			masterRect = newFrame;
			
			newFrame.origin.x += newFrame.size.width;
			newFrame.size.width = _splitWidth;
			dividerRect = newFrame;
			
			newFrame.origin.x += newFrame.size.width;
			newFrame.size.width = width - newFrame.origin.x;
			detailRect = newFrame;
			
		} else {
			if (!shouldShowMaster) {
				// Move off-screen.
				newFrame.size.width += (_splitPosition + _splitWidth);
			}
			
			newFrame.size.width -= (_splitPosition + _splitWidth);
			detailRect = newFrame;
			
			newFrame.origin.x += newFrame.size.width;
			newFrame.size.width = _splitWidth;
			dividerRect = newFrame;
			
			newFrame.origin.x += newFrame.size.width;
			newFrame.size.width = _splitPosition;
			masterRect = newFrame;
		}
		
		// Position master.
		controller = self.masterViewController;
		if (controller && [controller isKindOfClass:[UIViewController class]])  {
			theView = controller.view;
			if (theView) {
				theView.frame = masterRect;
				if (!theView.superview) {
					[controller viewWillAppear:NO];
					[self.view addSubview:theView];
					[controller viewDidAppear:NO];
				}
			}
		}
		
		// Position divider.
		theView = _dividerView;
		theView.frame = dividerRect;
		if (!theView.superview) {
			[self.view addSubview:theView];
		}
		
		// Position detail.
		controller = self.detailViewController;
		if (controller && [controller isKindOfClass:[UIViewController class]])  {
			theView = controller.view;
			if (theView) {
				theView.frame = detailRect;
				if (!theView.superview) {
					[self.view insertSubview:theView aboveSubview:self.masterViewController.view];
				} else {
					[self.view bringSubviewToFront:theView];
				}
			}
		}
		
	} else {
		// Master above, detail below (or vice versa).
		CGRect masterRect, dividerRect, detailRect;
		if (masterFirst) {
			if (!shouldShowMaster) {
				// Move off-screen.
				newFrame.origin.y -= (_splitPosition + _splitWidth);
			}
			
			newFrame.size.height = _splitPosition;
			masterRect = newFrame;
			
			newFrame.origin.y += newFrame.size.height;
			newFrame.size.height = _splitWidth;
			dividerRect = newFrame;
			
			newFrame.origin.y += newFrame.size.height;
			newFrame.size.height = height - newFrame.origin.y;
			detailRect = newFrame;
			
		} else {
			if (!shouldShowMaster) {
				// Move off-screen.
				newFrame.size.height += (_splitPosition + _splitWidth);
			}
			
			newFrame.size.height -= (_splitPosition + _splitWidth);
			detailRect = newFrame;
			
			newFrame.origin.y += newFrame.size.height;
			newFrame.size.height = _splitWidth;
			dividerRect = newFrame;
			
			newFrame.origin.y += newFrame.size.height;
			newFrame.size.height = _splitPosition;
			masterRect = newFrame;
		}
		
		// Position master.
		controller = self.masterViewController;
		if (controller && [controller isKindOfClass:[UIViewController class]])  {
			theView = controller.view;
			if (theView) {
				theView.frame = masterRect;
				if (!theView.superview) {
					[controller viewWillAppear:NO];
					[self.view addSubview:theView];
					[controller viewDidAppear:NO];
				}
			}
		}
		
		// Position divider.
		theView = _dividerView;
		theView.frame = dividerRect;
		if (!theView.superview) {
			[self.view addSubview:theView];
		}
		
		// Position detail.
		controller = self.detailViewController;
		if (controller && [controller isKindOfClass:[UIViewController class]])  {
			theView = controller.view;
			if (theView) {
				theView.frame = detailRect;
				if (!theView.superview) {
					[self.view insertSubview:theView aboveSubview:self.masterViewController.view];
				} else {
					[self.view bringSubviewToFront:theView];
				}
			}
		}
	}
	
	// Create corner views if necessary.
	MGSplitCornersView *leadingCorners; // top/left of screen in vertical/horizontal split.
	MGSplitCornersView *trailingCorners; // bottom/right of screen in vertical/horizontal split.
	if (!_cornerViews) {
		CGRect cornerRect = CGRectMake(0, 0, 10, 10); // arbitrary, will be resized below.
		leadingCorners = [[MGSplitCornersView alloc] initWithFrame:cornerRect];
		leadingCorners.splitViewController = self;
		leadingCorners.cornerBackgroundColor = MG_DEFAULT_CORNER_COLOR;
		leadingCorners.cornerRadius = MG_DEFAULT_CORNER_RADIUS;
		trailingCorners = [[MGSplitCornersView alloc] initWithFrame:cornerRect];
		trailingCorners.splitViewController = self;
		trailingCorners.cornerBackgroundColor = MG_DEFAULT_CORNER_COLOR;
		trailingCorners.cornerRadius = MG_DEFAULT_CORNER_RADIUS;
		_cornerViews = [[NSArray alloc] initWithObjects:leadingCorners, trailingCorners, nil];
		[leadingCorners release];
		[trailingCorners release];
		
	} else if ([_cornerViews count] == 2) {
		leadingCorners = [_cornerViews objectAtIndex:0];
		trailingCorners = [_cornerViews objectAtIndex:1];
	}
	
	// Configure and layout the corner-views.
	leadingCorners.cornersPosition = (_vertical) ? MGCornersPositionLeadingVertical : MGCornersPositionLeadingHorizontal;
	trailingCorners.cornersPosition = (_vertical) ? MGCornersPositionTrailingVertical : MGCornersPositionTrailingHorizontal;
	leadingCorners.autoresizingMask = (_vertical) ? UIViewAutoresizingFlexibleBottomMargin : UIViewAutoresizingFlexibleRightMargin;
	trailingCorners.autoresizingMask = (_vertical) ? UIViewAutoresizingFlexibleTopMargin : UIViewAutoresizingFlexibleLeftMargin;
	
	float x, y, cornersWidth, cornersHeight;
	CGRect leadingRect, trailingRect;
	float radius = leadingCorners.cornerRadius;
	if (_vertical) { // left/right split
		cornersWidth = (radius * 2.0) + _splitWidth;
		cornersHeight = radius;
		x = ((shouldShowMaster) ? ((masterFirst) ? _splitPosition : width - (_splitPosition + _splitWidth)) : (0 - _splitWidth)) - radius;
		y = 0;
		leadingRect = CGRectMake(x, y, cornersWidth, cornersHeight); // top corners
		trailingRect = CGRectMake(x, (height - cornersHeight), cornersWidth, cornersHeight); // bottom corners
		
	} else { // top/bottom split
		x = 0;
		y = ((shouldShowMaster) ? ((masterFirst) ? _splitPosition : height - (_splitPosition + _splitWidth)) : (0 - _splitWidth)) - radius;
		cornersWidth = radius;
		cornersHeight = (radius * 2.0) + _splitWidth;
		leadingRect = CGRectMake(x, y, cornersWidth, cornersHeight); // left corners
		trailingRect = CGRectMake((width - cornersWidth), y, cornersWidth, cornersHeight); // right corners
	}
	
	leadingCorners.frame = leadingRect;
	trailingCorners.frame = trailingRect;
	
	// Ensure corners are visible and frontmost.
	if (!leadingCorners.superview) {
		[self.view insertSubview:leadingCorners aboveSubview:self.detailViewController.view];
		[self.view insertSubview:trailingCorners aboveSubview:self.detailViewController.view];
	} else {
		[self.view bringSubviewToFront:leadingCorners];
		[self.view bringSubviewToFront:trailingCorners];
	}
}


- (void)layoutSubviewsWithAnimation:(BOOL)animate
{
	[self layoutSubviewsForInterfaceOrientation:self.interfaceOrientation withAnimation:animate];
}


- (void)layoutSubviews
{
	[self layoutSubviewsForInterfaceOrientation:self.interfaceOrientation withAnimation:YES];
}


- (void)viewWillAppear:(BOOL)animated
{
	[super viewWillAppear:animated];
	
	if ([self isShowingMaster]) {
		[self.masterViewController viewWillAppear:animated];
	}
	[self.detailViewController viewWillAppear:animated];
	
	_reconfigurePopup = YES;
	[self layoutSubviews];
}


- (void)viewDidAppear:(BOOL)animated
{
	[super viewDidAppear:animated];
	
	if ([self isShowingMaster]) {
		[self.masterViewController viewDidAppear:animated];
	}
	[self.detailViewController viewDidAppear:animated];
}


- (void)viewWillDisappear:(BOOL)animated
{
	[super viewWillDisappear:animated];
	
	if ([self isShowingMaster]) {
		[self.masterViewController viewWillDisappear:animated];
	}
	[self.detailViewController viewWillDisappear:animated];
}


- (void)viewDidDisappear:(BOOL)animated
{
	[super viewDidDisappear:animated];
	
	if ([self isShowingMaster]) {
		[self.masterViewController viewDidDisappear:animated];
	}
	[self.detailViewController viewDidDisappear:animated];
}


#pragma mark -
#pragma mark Popover handling


- (void)reconfigureForMasterInPopover:(BOOL)inPopover
{
	_reconfigurePopup = NO;
	
	if ((inPopover && _hiddenPopoverController) || (!inPopover && !_hiddenPopoverController) || !self.masterViewController) {
		// Nothing to do.
		return;
	}
	
	if (inPopover && !_hiddenPopoverController && !_barButtonItem) {
		// Create and configure popover for our masterViewController.
		[_hiddenPopoverController release];
		_hiddenPopoverController = nil;
		[self.masterViewController viewWillDisappear:NO];
		_hiddenPopoverController = [[UIPopoverController alloc] initWithContentViewController:self.masterViewController];
		[self.masterViewController viewDidDisappear:NO];
		
		// Create and configure _barButtonItem.
		_barButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Master", nil) 
														  style:UIBarButtonItemStyleBordered 
														 target:self 
														 action:@selector(showMasterPopover:)];
		
		// Inform delegate of this state of affairs.
		if (_delegate && [_delegate respondsToSelector:@selector(splitViewController:willHideViewController:withBarButtonItem:forPopoverController:)]) {
			[(NSObject <MGSplitViewControllerDelegate> *)_delegate splitViewController:self 
																willHideViewController:self.masterViewController 
																	 withBarButtonItem:_barButtonItem 
																  forPopoverController:_hiddenPopoverController];
		}
		
	} else if (!inPopover && _hiddenPopoverController && _barButtonItem) {
		// I know this looks strange, but it fixes a bizarre issue with UIPopoverController leaving masterViewController's views in disarray.
		[_hiddenPopoverController presentPopoverFromRect:CGRectZero inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:NO];
		
		// Remove master from popover and destroy popover, if it exists.
		[_hiddenPopoverController dismissPopoverAnimated:NO];
		[_hiddenPopoverController release];
		_hiddenPopoverController = nil;
		
		// Inform delegate that the _barButtonItem will become invalid.
		if (_delegate && [_delegate respondsToSelector:@selector(splitViewController:willShowViewController:invalidatingBarButtonItem:)]) {
			[(NSObject <MGSplitViewControllerDelegate> *)_delegate splitViewController:self 
																willShowViewController:self.masterViewController 
															 invalidatingBarButtonItem:_barButtonItem];
		}
		
		// Destroy _barButtonItem.
		[_barButtonItem release];
		_barButtonItem = nil;
		
		// Move master view.
		UIView *masterView = self.masterViewController.view;
		if (masterView && masterView.superview != self.view) {
			[masterView removeFromSuperview];
		}
	}
}


- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController
{
	[self reconfigureForMasterInPopover:NO];
}


- (void)notePopoverDismissed
{
	[self popoverControllerDidDismissPopover:_hiddenPopoverController];
}


#pragma mark -
#pragma mark Animations


- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{
	if (([animationID isEqualToString:MG_ANIMATION_CHANGE_SPLIT_ORIENTATION] || 
		 [animationID isEqualToString:MG_ANIMATION_CHANGE_SUBVIEWS_ORDER])
		&& _cornerViews) {
		for (UIView *corner in _cornerViews) {
			corner.hidden = NO;
		}
		_dividerView.hidden = NO;
	}
}


#pragma mark -
#pragma mark IB Actions


- (IBAction)toggleSplitOrientation:(id)sender
{
	BOOL showingMaster = [self isShowingMaster];
	if (showingMaster) {
		if (_cornerViews) {
			for (UIView *corner in _cornerViews) {
				corner.hidden = YES;
			}
			_dividerView.hidden = YES;
		}
		[UIView beginAnimations:MG_ANIMATION_CHANGE_SPLIT_ORIENTATION context:nil];
		[UIView setAnimationDelegate:self];
		[UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];
	}
	self.vertical = (!self.vertical);
	if (showingMaster) {
		[UIView commitAnimations];
	}
}


- (IBAction)toggleMasterBeforeDetail:(id)sender
{
	BOOL showingMaster = [self isShowingMaster];
	if (showingMaster) {
		if (_cornerViews) {
			for (UIView *corner in _cornerViews) {
				corner.hidden = YES;
			}
			_dividerView.hidden = YES;
		}
		[UIView beginAnimations:MG_ANIMATION_CHANGE_SUBVIEWS_ORDER context:nil];
		[UIView setAnimationDelegate:self];
		[UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];
	}
	self.masterBeforeDetail = (!self.masterBeforeDetail);
	if (showingMaster) {
		[UIView commitAnimations];
	}
}


- (IBAction)toggleMasterView:(id)sender
{
	if (_hiddenPopoverController && _hiddenPopoverController.popoverVisible) {
		[_hiddenPopoverController dismissPopoverAnimated:NO];
	}
	
	if (![self isShowingMaster]) {
		// We're about to show the master view. Ensure it's in place off-screen to be animated in.
		_reconfigurePopup = YES;
		[self reconfigureForMasterInPopover:NO];
		[self layoutSubviews];
	}
	
	// This action functions on the current primary orientation; it is independent of the other primary orientation.
	[UIView beginAnimations:@"toggleMaster" context:nil];
	if (self.isLandscape) {
		self.showsMasterInLandscape = !_showsMasterInLandscape;
	} else {
		self.showsMasterInPortrait = !_showsMasterInPortrait;
	}
	[UIView commitAnimations];
}


- (IBAction)showMasterPopover:(id)sender
{
	if (_hiddenPopoverController && !(_hiddenPopoverController.popoverVisible)) {
		// Inform delegate.
		if (_delegate && [_delegate respondsToSelector:@selector(splitViewController:popoverController:willPresentViewController:)]) {
			[(NSObject <MGSplitViewControllerDelegate> *)_delegate splitViewController:self 
																	 popoverController:_hiddenPopoverController 
															 willPresentViewController:self.masterViewController];
		}
		
		// Show popover.
		[_hiddenPopoverController presentPopoverFromBarButtonItem:_barButtonItem permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
	}
}


#pragma mark -
#pragma mark Accessors and properties


- (id)delegate
{
	return _delegate;
}


- (void)setDelegate:(id <MGSplitViewControllerDelegate>)newDelegate
{
	if (newDelegate != _delegate && 
		(!newDelegate || [(NSObject *)newDelegate conformsToProtocol:@protocol(MGSplitViewControllerDelegate)])) {
		_delegate = newDelegate;
	}
}


- (BOOL)showsMasterInPortrait
{
	return _showsMasterInPortrait;
}


- (void)setShowsMasterInPortrait:(BOOL)flag
{
	if (flag != _showsMasterInPortrait) {
		_showsMasterInPortrait = flag;
		
		if (![self isLandscape]) { // i.e. if this will cause a visual change.
			if (_hiddenPopoverController && _hiddenPopoverController.popoverVisible) {
				[_hiddenPopoverController dismissPopoverAnimated:NO];
			}
			
			// Rearrange views.
			_reconfigurePopup = YES;
			[self layoutSubviews];
		}
	}
}


- (BOOL)showsMasterInLandscape
{
	return _showsMasterInLandscape;
}


- (void)setShowsMasterInLandscape:(BOOL)flag
{
	if (flag != _showsMasterInLandscape) {
		_showsMasterInLandscape = flag;
		
		if ([self isLandscape]) { // i.e. if this will cause a visual change.
			if (_hiddenPopoverController && _hiddenPopoverController.popoverVisible) {
				[_hiddenPopoverController dismissPopoverAnimated:NO];
			}
			
			// Rearrange views.
			_reconfigurePopup = YES;
			[self layoutSubviews];
		}
	}
}


- (BOOL)isVertical
{
	return _vertical;
}


- (void)setVertical:(BOOL)flag
{
	if (flag != _vertical) {
		if (_hiddenPopoverController && _hiddenPopoverController.popoverVisible) {
			[_hiddenPopoverController dismissPopoverAnimated:NO];
		}
		
		_vertical = flag;
		
		// Inform delegate.
		if (_delegate && [_delegate respondsToSelector:@selector(splitViewController:willChangeSplitOrientationToVertical:)]) {
			[_delegate splitViewController:self willChangeSplitOrientationToVertical:_vertical];
		}
		
		[self layoutSubviews];
	}
}


- (BOOL)isMasterBeforeDetail
{
	return _masterBeforeDetail;
}


- (void)setMasterBeforeDetail:(BOOL)flag
{
	if (flag != _masterBeforeDetail) {
		if (_hiddenPopoverController && _hiddenPopoverController.popoverVisible) {
			[_hiddenPopoverController dismissPopoverAnimated:NO];
		}
		
		_masterBeforeDetail = flag;
		
		if ([self isShowingMaster]) {
			[self layoutSubviews];
		}
	}
}


- (float)splitPosition
{
	return _splitPosition;
}


- (void)setSplitPosition:(float)posn
{
	// Check to see if delegate wishes to constrain the position.
	float newPosn = posn;
	BOOL constrained = NO;
	CGSize fullSize = [self splitViewSizeForOrientation:self.interfaceOrientation];
	if (_delegate && [_delegate respondsToSelector:@selector(splitViewController:constrainSplitPosition:splitViewSize:)]) {
		newPosn = [_delegate splitViewController:self constrainSplitPosition:newPosn splitViewSize:fullSize];
		constrained = YES; // implicitly trust delegate's response.
		
	} else {
		// Apply default constraints if delegate doesn't wish to participate.
		float minPos = MG_MIN_VIEW_WIDTH;
		float maxPos = ((_vertical) ? fullSize.width : fullSize.height) - (MG_MIN_VIEW_WIDTH + _splitWidth);
		constrained = (newPosn != _splitPosition && newPosn >= minPos && newPosn <= maxPos);
	}
	
	if (constrained) {
		if (_hiddenPopoverController && _hiddenPopoverController.popoverVisible) {
			[_hiddenPopoverController dismissPopoverAnimated:NO];
		}
		
		_splitPosition = newPosn;
		
		// Inform delegate.
		if (_delegate && [_delegate respondsToSelector:@selector(splitViewController:willMoveSplitToPosition:)]) {
			[_delegate splitViewController:self willMoveSplitToPosition:_splitPosition];
		}
		
		if ([self isShowingMaster]) {
			[self layoutSubviews];
		}
	}
}


- (void)setSplitPosition:(float)posn animated:(BOOL)animate
{
	BOOL shouldAnimate = (animate && [self isShowingMaster]);
	if (shouldAnimate) {
		[UIView beginAnimations:@"SplitPosition" context:nil];
	}
	[self setSplitPosition:posn];
	if (shouldAnimate) {
		[UIView commitAnimations];
	}
}


- (float)splitWidth
{
	return _splitWidth;
}


- (void)setSplitWidth:(float)width
{
	if (width != _splitWidth && width >= 0) {
		_splitWidth = width;
		if ([self isShowingMaster]) {
			[self layoutSubviews];
		}
	}
}


- (NSArray *)viewControllers
{
	return [[_viewControllers copy] autorelease];
}


- (void)setViewControllers:(NSArray *)controllers
{
	if (controllers != _viewControllers) {
		for (UIViewController *controller in _viewControllers) {
			if ([controller isKindOfClass:[UIViewController class]]) {
				[controller.view removeFromSuperview];
			}
		}
		[_viewControllers release];
		_viewControllers = [[NSMutableArray alloc] initWithCapacity:2];
		if (controllers && [controllers count] >= 2) {
			self.masterViewController = [controllers objectAtIndex:0];
			self.detailViewController = [controllers objectAtIndex:1];
		} else {
			NSLog(@"Error: %@ requires 2 view-controllers. (%@)", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
		}
		
		[self layoutSubviews];
	}
}


- (UIViewController *)masterViewController
{
	if (_viewControllers && [_viewControllers count] > 0) {
		NSObject *controller = [_viewControllers objectAtIndex:0];
		if ([controller isKindOfClass:[UIViewController class]]) {
			return [[controller retain] autorelease];
		}
	}
	
	return nil;
}


- (void)setMasterViewController:(UIViewController *)master
{
	if (!_viewControllers) {
		_viewControllers = [[NSMutableArray alloc] initWithCapacity:2];
	}
	
	NSObject *newMaster = master;
	if (!newMaster) {
		newMaster = [NSNull null];
	}
	
	BOOL changed = YES;
	if ([_viewControllers count] > 0) {
		if ([_viewControllers objectAtIndex:0] == newMaster) {
			changed = NO;
		} else {
			[_viewControllers replaceObjectAtIndex:0 withObject:newMaster];
		}
		
	} else {
		[_viewControllers addObject:newMaster];
	}
	
	if (changed) {
		[self layoutSubviews];
	}
}


- (UIViewController *)detailViewController
{
	if (_viewControllers && [_viewControllers count] > 1) {
		NSObject *controller = [_viewControllers objectAtIndex:1];
		if ([controller isKindOfClass:[UIViewController class]]) {
			return [[controller retain] autorelease];
		}
	}
	
	return nil;
}


- (void)setDetailViewController:(UIViewController *)detail
{
	if (!_viewControllers) {
		_viewControllers = [[NSMutableArray alloc] initWithCapacity:2];
		[_viewControllers addObject:[NSNull null]];
	}
	
	BOOL changed = YES;
	if ([_viewControllers count] > 1) {
		if ([_viewControllers objectAtIndex:1] == detail) {
			changed = NO;
		} else {
			[_viewControllers replaceObjectAtIndex:1 withObject:detail];
		}
		
	} else {
		[_viewControllers addObject:detail];
	}
	
	if (changed) {
		[self layoutSubviews];
	}
}


- (MGSplitDividerView *)dividerView
{
	return [[_dividerView retain] autorelease];
}


- (void)setDividerView:(MGSplitDividerView *)divider
{
	if (divider != _dividerView) {
		[_dividerView removeFromSuperview];
		[_dividerView release];
		_dividerView = [divider retain];
		_dividerView.splitViewController = self;
		_dividerView.backgroundColor = MG_DEFAULT_CORNER_COLOR;
		if ([self isShowingMaster]) {
			[self layoutSubviews];
		}
	}
}


- (BOOL)allowsDraggingDivider
{
	if (_dividerView) {
		return _dividerView.allowsDragging;
	}
	
	return NO;
}


- (void)setAllowsDraggingDivider:(BOOL)flag
{
	if (self.allowsDraggingDivider != flag && _dividerView) {
		_dividerView.allowsDragging = flag;
	}
}


- (MGSplitViewDividerStyle)dividerStyle
{
	return _dividerStyle;
}


- (void)setDividerStyle:(MGSplitViewDividerStyle)newStyle
{
	if (_hiddenPopoverController && _hiddenPopoverController.popoverVisible) {
		[_hiddenPopoverController dismissPopoverAnimated:NO];
	}
	
	// We don't check to see if newStyle equals _dividerStyle, because it's a meta-setting.
	// Aspects could have been changed since it was set.
	_dividerStyle = newStyle;
	
	// Reconfigure general appearance and behaviour.
	float cornerRadius;
	if (_dividerStyle == MGSplitViewDividerStyleThin) {
		cornerRadius = MG_DEFAULT_CORNER_RADIUS;
		_splitWidth = MG_DEFAULT_SPLIT_WIDTH;
		self.allowsDraggingDivider = NO;
		
	} else if (_dividerStyle == MGSplitViewDividerStylePaneSplitter) {
		cornerRadius = MG_PANESPLITTER_CORNER_RADIUS;
		_splitWidth = MG_PANESPLITTER_SPLIT_WIDTH;
		self.allowsDraggingDivider = YES;
	}
	
	// Update divider and corners.
	[_dividerView setNeedsDisplay];
	if (_cornerViews) {
		for (MGSplitCornersView *corner in _cornerViews) {
			corner.cornerRadius = cornerRadius;
		}
	}
	
	// Layout all views.
	[self layoutSubviews];
}


- (void)setDividerStyle:(MGSplitViewDividerStyle)newStyle animated:(BOOL)animate
{
	BOOL shouldAnimate = (animate && [self isShowingMaster]);
	if (shouldAnimate) {
		[UIView beginAnimations:@"DividerStyle" context:nil];
	}
	[self setDividerStyle:newStyle];
	if (shouldAnimate) {
		[UIView commitAnimations];
	}
}


- (NSArray *)cornerViews
{
	if (_cornerViews) {
		return [[_cornerViews retain] autorelease];
	}
	
	return nil;
}


@synthesize showsMasterInPortrait;
@synthesize showsMasterInLandscape;
@synthesize vertical;
@synthesize delegate;
@synthesize viewControllers;
@synthesize masterViewController;
@synthesize detailViewController;
@synthesize dividerView;
@synthesize splitPosition;
@synthesize splitWidth;
@synthesize allowsDraggingDivider;
@synthesize dividerStyle;


@end