project_files/HedgewarsMobile/Classes/MGSplitViewController/MGSplitViewController.m
author nemo
Sat, 27 Apr 2013 17:02:26 -0400
changeset 8940 181ea27122da
parent 8441 a00b0fa0dbd7
child 11115 3729ac42189b
permissions -rwxr-xr-x
This is something we need to check in all translations. Occasionally the source phrase ends w/ a space. Need to make sure translations take this into account.

//
//  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