//
//  RBSplitSubview.m version 1.0.4
//  RBSplitView
//
//  Created by Rainer Brockerhoff on 19/11/2004.
//  Copyright 2004,2005 Rainer Brockerhoff. All rights reserved.
//

#import "RBSplitView.h"
#import "RBSplitViewPrivateDefines.h"

// This global points to the animation data structure while an animation is in
// progress; if there's none, it will be NULL. Animating may be very CPU-intensive so
// we allow only one animation to take place at a time.
static animationData* animation = NULL;

@implementation RBSplitSubview
// RBSplitSubview is the container view for each "split" of the RBSplitView. You shouldn't insert one into
// anything else but an RBSplitView, and RBSplitViews won't accept any other direct subview.
// Ordinarily you'd set up size limits and other attributes for each RBSplitSubview in Interface Builder
// and never touch it inside the program.
// RBSplitSubviews are different from normal views in that they don't have height or width but just a
// "dimension", measured along the RBSplitView's divider's orientation (horizontal or vertical). The "other"
// dimension is always set to cover the entire RBSplitView in that direction, so there's no way to get
// or set it directly.
// RBSplitSubviews are non-flipped.

// This class method returns YES if an animation is in progress.
+ (BOOL)animating {
	return animation!=NULL;
}

// This is the designated initializer for RBSplitSubview. It sets some reasonable defaults. However, you
// can't rely on anything working until you insert it into a RBSplitView.
- (id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
	fraction = 0.0;
	canCollapse = NO;
	notInLimits = NO;
	minDimension = 1.0;
	maxDimension = WAYOUT;
	identifier = @"";
	savedSize = frame.size;
	return self;
}

// Just releases our stuff when going away.
- (void)dealloc {
	[identifier release];
	[super dealloc];
}

// These return nil since we're not a RBSplitView (they're overridden there).
- (RBSplitView*)asSplitView {
	return nil;
}

- (RBSplitView*)coupledSplitView {
	return nil;
}

// Sets and gets the coupling between a RBSplitView and its containing RBSplitView (if any).
// For convenience, these methods are also implemented here.
- (void)setCoupled:(BOOL)flag {
}

- (BOOL)isCoupled {
	return NO;
}

// RBSplitSubviews are never flipped, unless they're RBSplitViews.
- (BOOL)isFlipped {
	return NO;
}

// We copy the opacity of the owning split view.
- (BOOL)isOpaque {
	return [[self couplingSplitView] isOpaque];
}

// RBSplitSubviews can never be hidden, but a non-nested RBSplitView can. We're handling this here
// rather than overriding it in RBSplitView, to be able to access super.
- (BOOL)isHidden {
	return [self splitView]?NO:[super isHidden];
}

- (void)setHidden:(BOOL)flag {
	if (![self splitView]) {
		[super setHidden:flag];
	}
}

// RBSplitSubviews can't be in the responder chain.
- (BOOL)acceptsFirstResponder {
	return NO;
}

// Mousing down should move the window only for a completely transparent background. This might have
// unintended side effects in metal windows, so for those you might want to use a background color
// with a very low alpha (0.01 for instance).
// This is commented out as I'm still experimenting with it.
/*- (BOOL)mouseDownCanMoveWindow {
	RBSplitView* sv = [self asSplitView];
	if (!sv) {
		sv = [self couplingSplitView];
	}
	return [sv background]==nil;
	return YES;
}*/

// This returns the owning splitview. It's guaranteed to return a RBSplitView or nil.
// You should avoid having "orphan" RBSplitSubviews, or at least manipulating
// them while they're not inserted in a RBSplitView.
- (RBSplitView*)splitView {
	id result = [self superview];
	if ([result isKindOfClass:[RBSplitView class]]) {
		return (RBSplitView*)result;
	}
	return nil;
}

// This also returns the owning splitview. It's overridden for nested RBSplitViews.
- (RBSplitView*)couplingSplitView {
	id result = [self superview];
	if ([result isKindOfClass:[RBSplitView class]]) {
		return (RBSplitView*)result;
	}
	return nil;
}

// This returns the outermost directly containing RBSplitView, or nil.
- (RBSplitView*)outermostSplitView {
	id result = nil;
	id sv = self;
	while ((sv = [sv superview])&&[sv isKindOfClass:[RBSplitView class]]) {
		result = sv;
	}
	return result;
}

// This convenience method returns YES if the containing RBSplitView is horizontal.
- (BOOL)splitViewIsHorizontal {
	return [[self splitView] isHorizontal];
}

// You can use either tags (ints) or identifiers (NSStrings) to identify individual subviews.
// We take care not to have nil identifiers.
- (void)setTag:(int)theTag {
	tag = theTag;
}

- (int)tag {
	return tag;
}

- (void)setIdentifier:(NSString*)aString {
	[identifier autorelease];
	identifier = aString?[aString retain]:@"";
}

- (NSString*)identifier {
	return identifier;
}

// If we have an identifier, this will make debugging a little easier by appending it to the
// default description.
- (NSString*)description {
	return [identifier length]>0?[NSString stringWithFormat:@"%@(%@)",[super description],identifier]:[super description];
}

// This pair of methods allows you to get and change the position of a subview (within the split view);
// this counts from zero from the left or top of the split view.
- (unsigned)position {
	RBSplitView* sv = [self splitView];
	return sv?[[sv subviews] indexOfObjectIdenticalTo:self]:0;
}

- (void)setPosition:(unsigned)newPosition {
	RBSplitView* sv = [self splitView];
	if (sv) {
		[self retain];
		[self removeFromSuperviewWithoutNeedingDisplay];
		NSArray* subviews = [sv subviews];
		if (newPosition>=[subviews count]) {
			[sv addSubview:self positioned:NSWindowAbove relativeTo:nil];
		} else {
			[sv addSubview:self positioned:NSWindowBelow relativeTo:[subviews objectAtIndex:newPosition]];
		}
		[self release];
	}
}

// Tests whether the subview is collapsed.
- (BOOL)isCollapsed {
	return [self RB___visibleDimension]<=0.0;
}

// Returns the subview's status.
- (RBSSubviewStatus)status {
	animationData* anim = [self RB___animationData:NO];
	if (anim) {
		return anim->collapsing?RBSSubviewCollapsing:RBSSubviewExpanding;
	}
	return [self RB___visibleDimension]<=0.0?RBSSubviewCollapsed:RBSSubviewNormal;
}

// Tests whether the subview can be collapsed. The local instance variable will be overridden by the
// delegate method if it's implemented.
- (BOOL)canCollapse {
	BOOL result = canCollapse;
	RBSplitView* sv = [self splitView];
	if ([sv numberOfSubviews]<2) {
		return NO;
	}
	id delegate = [sv delegate];
	if ([delegate respondsToSelector:@selector(splitView:canCollapse:)]) {
		result = [delegate splitView:sv canCollapse:self];
	}
	return result;
}

// This sets the subview's "canCollapse" flag. Ignored if the delegate's splitView:canCollapse:
// method is implemented.
- (void)setCanCollapse:(BOOL)flag {
	canCollapse = flag;
}

// This expands a collapsed subview and calls the delegate's splitView:didExpand: method, if it exists.
// This is not called internally by other methods; call this to expand a subview programmatically.
// As a convenience to other methods, it returns the subview's dimension after expanding (this may be
// off by 1 pixel due to rounding) or 0.0 if it couldn't be expanded.
// The delegate should not change the subview's frame.
- (float)expand {
	return [self RB___expandAndSetToMinimum:NO];
}

// This collapses an expanded subview and calls the delegate's splitView:didCollapse: method, if it exists.
// This is not called internally by other methods; call this to expand a subview programmatically.
// As a convenience to other methods, it returns the negative of the subview's dimension before
// collapsing (or 0.0 if it couldn't be collapsed).
// The delegate should not change the subview's frame.
- (float)collapse {
	return [self RB___collapse];
}

// This tries to collapse the subview with animation, and collapses it instantly if some other
// subview is animating. Returns YES if animation was started successfully.
- (BOOL)collapseWithAnimation {
	if ([self status]==RBSSubviewNormal) {
		if ([self canCollapse]) {
			if ([self RB___animationData:YES]) {
				[self RB___stepAnimation];
				return YES;
			} else {
				[self RB___collapse];
			}
		}
	}
	return NO;
}

// This tries to expand the subview with animation, and expands it instantly if some other
// subview is animating. Returns YES if animation was started successfully.
- (BOOL)expandWithAnimation {
	if ([self status]==RBSSubviewCollapsed) {
		if ([self RB___animationData:YES]) {
			[self RB___stepAnimation];
			return YES;
		} else {
			[self RB___expandAndSetToMinimum:NO];
		}
	}
	return NO;
}

// These 3 methods get and set the view's minimum and maximum dimensions.
// The minimum dimension ought to be an integer at least equal to 1.0 but we make sure.
// The maximum dimension ought to be an integer at least equal to the minimum. As a convenience,
// pass in zero to set it to some huge number.
- (float)minDimension {
	return minDimension;
}

- (float)maxDimension {
	return maxDimension;
}

- (void)setMinDimension:(float)newMinDimension andMaxDimension:(float)newMaxDimension {
	minDimension = MAX(1.0,floorf(newMinDimension));
	if (newMaxDimension<1.0) {
		newMaxDimension = WAYOUT;
	}
	maxDimension = MAX(minDimension,floorf(newMaxDimension));
	float dim = [self dimension];
	if ((dim<minDimension)||(dim>maxDimension)) {
		[[self splitView] setMustAdjust];
	}
}

// This returns the subview's dimension. If it's collapsed, it returns the dimension it would have
// after expanding.
- (float)dimension {
	float dim = [self RB___visibleDimension];
	if (dim<=0.0) {
		dim = [[self splitView] RB___dimensionWithoutDividers]*fraction;
		if (dim<minDimension) {
			dim = minDimension;
		} else if (dim>maxDimension) {
			dim = maxDimension;
		}
	}
	return dim;
}

// Sets the current dimension of the subview, subject to the current maximum and minimum.
// If the subview is collapsed, this will have an effect only after reexpanding.
- (void)setDimension:(float)value {
	RBSplitView* sv = [self splitView];
	NSSize size = [self frame].size;
	BOOL ishor = [sv isHorizontal];
	if (DIM(size)) {
// We're not collapsed, save the minimum and maximum dimensions and set them temporarily
// to the desired value while adjusting subviews.
		float savemin,savemax;
		value = [self RB___setMinAndMaxTo:value savingMin:&savemin andMax:&savemax];
		DIM(size) = value;
		[self RB___setFrameSize:size withFraction:0.0];
		[sv adjustSubviews];
		minDimension = savemin;
		maxDimension = savemax;
	} else {
// We're collapsed, adjust the fraction so that we'll have the (approximately) correct
// dimension after expanding.
		fraction = value/[sv RB___dimensionWithoutDividers];
	}
}

// This just draws the background of a subview.
- (void)drawRect:(NSRect)rect {
	NSColor* bg = [[self splitView] background];
	if (bg) {
		[bg set];
		NSRectFillUsingOperation(rect,NSCompositeSourceOver);
	}
}

// We check if the RBSplitView must be adjusted before redisplaying programmatically.
// if so, we adjust and display the whole RBSplitView.
- (void)display {
	RBSplitView* sv = [self splitView];
	if (sv) {
		if ([sv mustAdjust]) {
			[sv display];
		} else {
			[super display];
		}
	}
}

// This pair of methods does nothing, unless we're a non-nested RBSplitView. As setFrame: calls them,
// it will do nothing either. RBSplitSubviews should always be resized by calling the two methods with
// the fraction: parameters. 
- (void)setFrameOrigin:(NSPoint)newOrigin {
	if (![self splitView]) {
		[super setFrameOrigin:newOrigin];
	}
}

- (void)setFrameSize:(NSSize)newSize {
	if (![self splitView]) {
		[super setFrameSize:newSize];
	}
}

// RBSplitSubviews will always resize their own subviews.
- (BOOL)autoresizesSubviews {
	return YES;
}

// This is method is called automatically when the subview is resized; don't call it yourself.
- (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize {
	RBSplitView* sv = [self splitView];
	if (sv) {
		BOOL ishor = [sv isHorizontal];
		NSRect frame = [self frame];
		float dim = DIM(frame.size);
		float other = OTHER(frame.size);
// We resize subviews only when we're inside the subview's limits and the containing splitview's limits.
		if ((dim>=minDimension)&&(dim<=maxDimension)&&(other>=[sv minDimension])&&(other<=[sv maxDimension])) {
			if (notInLimits) {
// The subviews can be resized, so we restore the saved size.
				oldBoundsSize = savedSize;
			}
// We save the size every time the subview's subviews are resized within the limits.
			notInLimits = NO;
			savedSize = frame.size;
			[super resizeSubviewsWithOldSize:oldBoundsSize];
		} else {
			notInLimits = YES;
		}
	}
}

// This method is used internally when a divider is dragged. It tries to change the subview's dimension
// and returns the actual change, collapsing or expanding whenever possible. You usually won't need
// to call this directly.
- (float)changeDimensionBy:(float)increment mayCollapse:(BOOL)mayCollapse {
	RBSplitView* sv = [self splitView];
	if (!sv||(increment==0.0)) {
		return 0.0;
	}
	BOOL ishor = [sv isHorizontal];
	NSRect frame = [self frame];
	float olddim = DIM(frame.size);
	float newdim = MAX(0.0,olddim+increment);
	if (newdim<olddim) {
		if (newdim<minDimension) {
// Collapse if needed
			if (mayCollapse&&[self canCollapse]&&(newdim<MAX(1.0,minDimension*(0.5-HYSTERESIS)))) {
				return [self RB___collapse];
			}
			newdim = minDimension;
		}
	} else if (newdim>olddim) {
		if (olddim<1.0) {
// Expand if needed.
			if (newdim>(minDimension*(0.5+HYSTERESIS))) {
				newdim = MAX(newdim,[self RB___expandAndSetToMinimum:YES]);
			} else {
				return 0.0;
			}
		}
		if (newdim>maxDimension) {
			newdim = maxDimension;
		}
	}
	if (newdim!=olddim) {
// The dimension has changed.
		DIM(frame.size) = newdim;
		[super setFrameSize:frame.size];
		[sv RB___setMustClearFractions];
		[sv setMustAdjust];
	}
	return newdim-olddim;
}

// This convenience method returns the number of subviews (surprise!)
- (unsigned)numberOfSubviews {
	return [[self subviews] count];
}

// This method handles clicking and dragging in an empty portion of the subview.
- (void)mouseDown:(NSEvent*)theEvent {
	NSWindow* window = [self window];
	if ([window isMovableByWindowBackground]&&![[self couplingSplitView] background]) {
// If we get here, it's a textured (metal) window, the mouse has gone down on an empty portion
// of the subview, and our RBSplitView has a transparent background. RBSplitView returns NO to
// mouseDownCanMoveWindow, but the window should move here - after all, the window background
// is visible right here! So we fake it and move the window as intended. Mwahahaha!
		NSPoint where =  [window convertBaseToScreen:[theEvent locationInWindow]];
		NSPoint origin = [window frame].origin;
// Now we loop handling mouse events until we get a mouse up event.
		while ((theEvent = [NSApp nextEventMatchingMask:NSLeftMouseDownMask|NSLeftMouseDraggedMask|NSLeftMouseUpMask untilDate:[NSDate distantFuture] inMode:NSEventTrackingRunLoopMode dequeue:YES])&&([theEvent type]!=NSLeftMouseUp)) {
// Set up a local autorelease pool for the loop to prevent buildup of temporary objects.
			NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
			NSPoint now = [window convertBaseToScreen:[theEvent locationInWindow]];
			origin.x += now.x-where.x;
			origin.y += now.y-where.y;
// Move the window by the mouse displacement since the last event.
			[window setFrameOrigin:origin];
			where = now;
			[pool release];
		}
	}
}

// These two methods encode and decode subviews.
- (void)encodeWithCoder:(NSCoder*)coder {
	NSRect frame;
	BOOL coll = [self isCollapsed];
	if (coll) {
// We can't encode a collapsed subview as-is, so we correct the frame size first and add WAYOUT
// to the origin to signal it was collapsed. 
		NSRect newf = frame = [self frame];
		newf.origin.x += WAYOUT;
		[super setFrameOrigin:newf.origin];
		newf.size = savedSize;
		[super setFrameSize:newf.size];
	}
	[super encodeWithCoder:coder];
	if (coll) {
		[super setFrame:frame];
	}
	if ([coder allowsKeyedCoding]) {
		[coder encodeObject:identifier forKey:@"identifier"];
		[coder encodeInt:tag forKey:@"tag"];
		[coder encodeFloat:minDimension forKey:@"minDimension"];
		[coder encodeFloat:maxDimension forKey:@"maxDimension"];
		[coder encodeDouble:fraction forKey:@"fraction"];
		[coder encodeBool:canCollapse forKey:@"canCollapse"];
	} else {
		[coder encodeObject:identifier];
		[coder encodeValueOfObjCType:@encode(int) at:&tag];
		[coder encodeValueOfObjCType:@encode(float) at:&minDimension];
		[coder encodeValueOfObjCType:@encode(float) at:&maxDimension];
		[coder encodeValueOfObjCType:@encode(double) at:&fraction];
		[coder encodeValueOfObjCType:@encode(BOOL) at:&canCollapse];
	}
}

- (id)initWithCoder:(NSCoder*)coder {
	fraction = 0.0;
	canCollapse = NO;
	notInLimits = NO;
	minDimension = 1.0;
	maxDimension = WAYOUT;
	identifier = @"";
    if ((self = [super initWithCoder:coder])) {
		NSRect frame = [self frame];
		savedSize = frame.size;
		if (frame.origin.x>=WAYOUT) {
// The subview was collapsed when encoded, so we correct the origin and collapse it.
			BOOL ishor = [self splitViewIsHorizontal];
			frame.origin.x -= WAYOUT;
			DIM(frame.size) = 0.0;
			[self setFrameOrigin:frame.origin];
			[self setFrameSize:frame.size];
		}
		if ([coder allowsKeyedCoding]) {
			[self setIdentifier:[coder decodeObjectForKey:@"identifier"]];
			tag = [coder decodeIntForKey:@"tag"];
			minDimension = [coder decodeFloatForKey:@"minDimension"];
			maxDimension = [coder decodeFloatForKey:@"maxDimension"];
			fraction = [coder decodeDoubleForKey:@"fraction"];
			canCollapse = [coder decodeBoolForKey:@"canCollapse"];
		} else {
			[self setIdentifier:[coder decodeObject]];
			[coder decodeValueOfObjCType:@encode(int) at:&tag];
			[coder decodeValueOfObjCType:@encode(float) at:&minDimension];
			[coder decodeValueOfObjCType:@encode(float) at:&maxDimension];
			[coder decodeValueOfObjCType:@encode(double) at:&fraction];
			[coder decodeValueOfObjCType:@encode(BOOL) at:&canCollapse];
		}
	}
    return self;
}

@end

@implementation RBSplitSubview (RB___SubviewAdditions)

// This internal method returns the current animationData. It will always return nil if
// the receiver isn't the current owner and some other subview is already being animated.
// Otherwise, if the parameter is YES, a new animation will be started (or the current
// one will be restarted).
- (animationData*)RB___animationData:(BOOL)start {
	if (animation&&(animation->owner!=self)) {
// There already is an animation in progress on some other subview.
		return nil;
	}
	if (start) {
// We want to start (or restart) an animation.
		RBSplitView* sv = [self splitView];
		if (sv) {
			float dim = [self dimension];
// First assume the default time, then ask the delegate.
			NSTimeInterval total = dim*(0.2/150.0);
			id delegate = [sv delegate];
			if ([delegate respondsToSelector:@selector(splitView:willAnimateSubview:withDimension:)]) {
				total = [delegate splitView:sv willAnimateSubview:self withDimension:dim];
			}
// No use animating anything shorter than the frametime.
			if (total>FRAMETIME) {
				if (!animation) {
					animation = (animationData*)malloc(sizeof(animationData));
				}
				if (animation) {
					animation->owner = self;
					animation->stepsDone = 0;
					animation->elapsedTime = 0.0;
					animation->dimension = dim;
					animation->collapsing = ![self isCollapsed];
					animation->totalTime = total;
					animation->finishTime = [NSDate timeIntervalSinceReferenceDate]+total;
				}
			} else if (animation) {
				free(animation);
				animation = NULL;
			}
		}
	}
	return animation;
}

// This internal method steps the animation to the next frame.
- (void)RB___stepAnimation {
	NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
	animationData* animation = [self RB___animationData:NO];
	if (animation) {
		RBSplitView* sv = [self splitView];
		NSTimeInterval remain = animation->finishTime-now;
		NSSize size = [self frame].size;
		BOOL ishor = [sv isHorizontal];
// Continuing animation only makes sense if we still have at least FRAMETIME available.
		if (remain>=FRAMETIME) {
			float dim = DIM(size);
			float avg = animation->elapsedTime;
// We try to keep a record of how long it takes, on the average, to resize and adjust
// one animation frame.
			if (animation->stepsDone) {
				avg /= animation->stepsDone;
			}
			NSTimeInterval delay = MIN(0.0,FRAMETIME-avg);
// We adjust the new dimension proportionally to how much of the designated time has passed.
			dim = floorf(animation->dimension*(remain-avg)/animation->totalTime);
			if (dim>4.0) {
				if (!animation->collapsing) {
					dim = animation->dimension-dim;
				} 
				DIM(size) = dim;
				[self RB___setFrameSize:size withFraction:0.0];
				[sv adjustSubviews];
				[self display];
				animation->elapsedTime += [NSDate timeIntervalSinceReferenceDate]-now;
				++animation->stepsDone;
// Schedule a timer to do the next animation step.
				[self performSelector:@selector(RB___stepAnimation) withObject:nil afterDelay:delay inModes:[NSArray arrayWithObjects:NSDefaultRunLoopMode,NSModalPanelRunLoopMode,
					NSEventTrackingRunLoopMode,nil]];
				return;
			}
		}
// We're finished, either collapse or expand entirely now.
		if (animation->collapsing) {
			DIM(size) = 0.0;
			[self RB___finishCollapse:size withFraction:animation->dimension/[sv RB___dimensionWithoutDividers]];
		} else {
			DIM(size) = animation->dimension;
			[self RB___setFrameSize:size withFraction:0.0];
			[sv adjustSubviews];
			[self RB___finishExpand];
		}
	}
}

// This internal method stops the animation, if the receiver is being animated. It will
// return YES if the animation was stopped.
- (BOOL)RB___stopAnimation {
	if (animation&&(animation->owner==self)) {
		[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(RB___stepAnimation) object:nil];
		free(animation);
		animation = NULL;
		return YES;
	}
	return NO;
}

// This internal method returns the actual visible dimension of the subview. Differs from -dimension in
// that it returns 0.0 if the subview is collapsed.
- (float)RB___visibleDimension {
	BOOL ishor = [self splitViewIsHorizontal];
	NSRect frame = [self frame];
	return MAX(0.0,DIM(frame.size));
}

// This pair of internal methods is used only inside -[RBSplitView adjustSubviews] to copy subview data
// from and to that method's internal cache.
- (void)RB___copyIntoCache:(subviewCache*)cache {
	cache->sub = self;
	cache->rect = [self frame];
	cache->size = [self RB___visibleDimension];
	cache->fraction = fraction;
	cache->constrain = NO;
}

- (void)RB___updateFromCache:(subviewCache*)cache withTotalDimension:(float)value {
	float dim = [self RB___visibleDimension];
	if (cache->size>0.0) {
		[self RB___setFrame:cache->rect withFraction:cache->fraction];
		if (dim<=0.0) {
			[self RB___finishExpand];
		}
	} else {
		if (dim>0.0) {
			[self RB___finishCollapse:cache->rect.size withFraction:dim/value];
		}
	}
}

// This internal method sets minimum and maximum values to the same value, saves the old values,
// and returns the new value (which will be limited to the old values).
- (float)RB___setMinAndMaxTo:(float)value savingMin:(float*)oldmin andMax:(float*)oldmax {
	*oldmin = [self minDimension];
	*oldmax = [self maxDimension];
	if (value<*oldmin) {
		value = *oldmin;
	}
	if (value>*oldmax) {
		value = *oldmax;
	}
	minDimension = maxDimension = value;
	return value;
}

// This internal method collapses a subview.
// It returns the negative of the size of the subview before collapsing, or 0.0 if it wasn't collapsed.
- (float)RB___collapse {
	float result = 0.0;
	if (![self isCollapsed]) {
		RBSplitView* sv = [self splitView];
		if (sv&&[self canCollapse]) {
			NSSize size = [self frame].size;
			BOOL ishor = [sv isHorizontal];
			result = DIM(size);
// For collapsed views, fraction will contain the fraction of the dimension previously occupied
			DIM(size) = 0.0;
			[self RB___finishCollapse:size withFraction:result/[sv RB___dimensionWithoutDividers]];
		}
	}
	return -result;
}

// This internal method finishes the collapse of a subview, setting its fraction, stopping the
// animation if there is one, and calling the delegate method if there is one.
- (void)RB___finishCollapse:(NSSize)size withFraction:(double)value {
	RBSplitView* sv = [self splitView];
	[self RB___setFrameSize:size withFraction:value];
	[sv RB___setMustClearFractions];
	if ([self RB___stopAnimation]) {
		[self display];
	}
	id delegate = [sv delegate];
	if ([delegate respondsToSelector:@selector(splitView:didCollapse:)]) {
		[delegate splitView:sv didCollapse:self];
	}
}

// This internal method expands a subview. setToMinimum will usually be YES during a divider drag.
// It returns the size of the subview after expanding, or 0.0 if it wasn't expanded.
- (float)RB___expandAndSetToMinimum:(BOOL)setToMinimum {
	float result = 0.0;
	RBSplitView* sv = [self splitView];
	if (sv&&[self isCollapsed]) {
		NSSize size = [super frame].size;
		double frac = fraction;
		BOOL ishor = [sv isHorizontal];
		if (setToMinimum) {
			result = DIM(size) = minDimension;
		} else {
			result = [sv RB___dimensionWithoutDividers]*frac;
// We need to apply a compensation factor for proportional resizing in adjustSubviews.
			float newdim = floorf((frac==1.0)?result:result/(1.0-frac));
			DIM(size) = newdim;
			result = floorf(result);
		}
		[self RB___setFrameSize:size withFraction:0.0];
		[self RB___finishExpand];
	}
	return result;
}

// This internal method finishes the the expansion of a subview, stopping the animation if
// there is one, and calling the delegate method if there is one.
- (void)RB___finishExpand {
	RBSplitView* sv = [self splitView];
	[sv RB___setMustClearFractions];
	if ([self RB___stopAnimation]) {
		[self display];
	}
	id delegate = [sv delegate];
	if ([delegate respondsToSelector:@selector(splitView:didExpand:)]) {
		[delegate splitView:sv didExpand:self];
	}
}

// This pair of internal methods sets the subview's frame or size, and also store a fraction value
// which is used to ensure repeatability when the whole split view is resized.
- (void)RB___setFrame:(NSRect)rect withFraction:(double)value {
	[super setFrameOrigin:rect.origin];
	[super setFrameSize:rect.size];
	fraction = value;
	[[self splitView] setMustAdjust];
	[[self asSplitView] setMustAdjust];
}

- (void)RB___setFrameSize:(NSSize)size withFraction:(double)value {
	[super setFrameSize:size];
	fraction = value;
	[[self splitView] setMustAdjust];
	[[self asSplitView] setMustAdjust];
}

// This internal method gets the fraction value.
- (double)RB___fraction {
	return fraction;
}

@end

