// // RBSplitSubview.m version 1.1.4 // RBSplitView // // Created by Rainer Brockerhoff on 19/11/2004. // Copyright 2004-2006 Rainer Brockerhoff. // Some Rights Reserved under the Creative Commons Attribution License, version 2.5, and/or the MIT License. // #import "RBSplitView.h" #import "RBSplitViewPrivateDefines.h" // This variable 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* currentAnimation = NULL; @implementation RBSplitSubview // This class method returns YES if an animation is in progress. + (BOOL)animating { return currentAnimation!=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]; if (self) { fraction = 0.0; canCollapse = NO; notInLimits = NO; minDimension = 1.0; maxDimension = WAYOUT; identifier = @""; previous = NSZeroRect; savedSize = frame.size; actDivider = NSNotFound; canDragWindow = NO; } 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]; } // A hidden RBSplitSubview is not redrawn and is not considered for drawing dividers. // This won't work before 10.3, though. - (void)setHidden:(BOOL)flag { if ([self isHidden]!=flag) { RBSplitView* sv = [self splitView]; [self RB___setHidden:flag]; if (flag) { [sv adjustSubviews]; } else { [sv adjustSubviewsExcepting:self]; } } } // 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; } // Tests whether the subview can shrink further. - (BOOL)canShrink { return [self RB___visibleDimension]>([self canCollapse]?0.0:minDimension); } // Tests whether the subview can expand further. - (BOOL)canExpand { return [self RB___visibleDimension]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 RB___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 { return [self collapseWithAnimation:YES withResize:YES]; } // 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 { return [self expandWithAnimation:YES withResize:YES]; } // These methods collapse and expand subviews with animation, depending on the parameters. // They return YES if animation startup was successful. If resize is NO, the subview is // collapsed/expanded without resizing it during animation. - (BOOL)collapseWithAnimation:(BOOL)animate withResize:(BOOL)resize { if ([self status]==RBSSubviewNormal) { if ([self canCollapse]) { if (animate&&[self RB___animationData:YES resize:resize]) { [self RB___clearResponder]; [self RB___stepAnimation]; return YES; } else { [self RB___collapse]; } } } return NO; } - (BOOL)expandWithAnimation:(BOOL)animate withResize:(BOOL)resize { if ([self status]==RBSSubviewCollapsed) { if (animate&&[self RB___animationData:YES resize:resize]) { [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 ((dimmaxDimension)) { [[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 (dimmaxDimension) { 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)>0.0) { // We're not collapsed, set the size and adjust other subviews. DIM(size) = value; [self setFrameSize:size]; [sv adjustSubviewsExcepting:self]; } 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, then tells the delegate, if any. // The delegate would usually draw a frame inside the subview. - (void)drawRect:(NSRect)rect { RBSplitView* sv = [self splitView]; NSColor* bg = [sv background]; if (bg) { [bg set]; NSRectFillUsingOperation(rect,NSCompositeSourceOver); } id del = [sv delegate]; if ([del respondsToSelector:@selector(splitView:willDrawSubview:inRect:)]) { [del splitView:sv willDrawSubview:self inRect:rect]; } } // 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]; } } } // 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. animationData* anim = [self RB___animationData:NO resize:NO]; if ((dim>=(anim&&!anim->resizing?anim->dimension: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 move:(BOOL)move { RBSplitView* sv = [self splitView]; if (!sv||(fabsf(increment)<1.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 (newdimolddim) { 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 ((int)newdim!=(int)olddim) { // The dimension has changed. increment = newdim-olddim; DIM(frame.size) = newdim; if (move) { DIM(frame.origin) -= increment; } // We call super instead of self here to postpone adjusting subviews for nested splitviews. // [super setFrameSize:frame.size]; [super setFrame:frame]; [sv RB___setMustClearFractions]; [sv setMustAdjust]; } return newdim-olddim; } // This convenience method returns the number of subviews (surprise!) - (unsigned)numberOfSubviews { return [[self subviews] count]; } // We return the deepest subview that's hit by aPoint. We also check with the delegate if aPoint is // within an alternate drag view. - (NSView*)hitTest:(NSPoint)aPoint { RBSplitView* sv = [self splitView]; if ([self mouse:aPoint inRect:[self frame]]) { id delegate = [sv delegate]; if ([delegate respondsToSelector:@selector(splitView:dividerForPoint:inSubview:)]) { actDivider = [delegate splitView:sv dividerForPoint:aPoint inSubview:self]; if ((int)actDivider<(int)([sv RB___numberOfSubviews]-1)) { return self; } } actDivider = NSNotFound; NSView* result = [super hitTest:aPoint]; canDragWindow = ![result isOpaque]; return result; } return nil; } // This method handles clicking and dragging in an empty portion of the subview, or in an alternate // drag view as designated by the delegate. - (void)mouseDown:(NSEvent*)theEvent { NSWindow* window = [self window]; NSPoint where = [theEvent locationInWindow]; if (actDivider=WAYOUT) { // The subview was collapsed when encoded, so we correct the origin and collapse it. BOOL ishor = [self splitViewIsHorizontal]; previous.origin.x -= WAYOUT; DIM(previous.size) = 0.0; [self setFrameOrigin:previous.origin]; [self setFrameSize:previous.size]; } previous = NSZeroRect; 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(typeof(tag)) at:&tag]; [coder decodeValueOfObjCType:@encode(typeof(minDimension)) at:&minDimension]; [coder decodeValueOfObjCType:@encode(typeof(maxDimension)) at:&maxDimension]; [coder decodeValueOfObjCType:@encode(typeof(fraction)) at:&fraction]; [coder decodeValueOfObjCType:@encode(typeof(canCollapse)) at:&canCollapse]; } } return self; } @end @implementation RBSplitSubview (RB___SubviewAdditions) // This hides/shows the subview without calling adjustSubview. - (void)RB___setHidden:(BOOL)flag { [super setHidden:flag]; } // 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 resize:(BOOL)resize { if (currentAnimation&&(currentAnimation->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 (!currentAnimation) { currentAnimation = (animationData*)malloc(sizeof(animationData)); } if (currentAnimation) { currentAnimation->owner = self; currentAnimation->stepsDone = 0; currentAnimation->elapsedTime = 0.0; currentAnimation->dimension = dim; currentAnimation->collapsing = ![self isCollapsed]; currentAnimation->totalTime = total; currentAnimation->finishTime = [NSDate timeIntervalSinceReferenceDate]+total; currentAnimation->resizing = resize; [sv RB___setDragging:YES]; } } else if (currentAnimation) { free(currentAnimation); currentAnimation = NULL; } } } return currentAnimation; } // This internal method steps the animation to the next frame. - (void)RB___stepAnimation { NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate]; animationData* anim = [self RB___animationData:NO resize:NO]; if (anim) { RBSplitView* sv = [self splitView]; NSTimeInterval remain = anim->finishTime-now; NSRect frame = [self frame]; BOOL ishor = [sv isHorizontal]; // Continuing animation only makes sense if we still have at least FRAMETIME available. if (remain>=FRAMETIME) { float dim = DIM(frame.size); float avg = anim->elapsedTime; // We try to keep a record of how long it takes, on the average, to resize and adjust // one animation frame. if (anim->stepsDone) { avg /= anim->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(anim->dimension*(remain-avg)/anim->totalTime); if (dim>4.0) { if (!anim->collapsing) { dim = anim->dimension-dim; } DIM(frame.size) = dim; [self RB___setFrame:frame withFraction:0.0 notify:NO]; [sv adjustSubviews]; [self display]; anim->elapsedTime += [NSDate timeIntervalSinceReferenceDate]-now; ++anim->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 (anim->collapsing) { DIM(frame.size) = 0.0; [self RB___finishCollapse:frame withFraction:anim->dimension/[sv RB___dimensionWithoutDividers]]; } else { float savemin,savemax; float dim = [self RB___setMinAndMaxTo:anim->dimension savingMin:&savemin andMax:&savemax]; DIM(frame.size) = dim; [self RB___finishExpand:frame withFraction:0.0]; minDimension = savemin; maxDimension = savemax; } } } // 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 (currentAnimation&&(currentAnimation->owner==self)) { [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(RB___stepAnimation) object:nil]; free(currentAnimation); currentAnimation = NULL; [[self splitView] RB___setDragging:NO]; 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>=1.0) { // New state is not collapsed. if (dim>=1.0) { // Old state was not collapsed, so we just change the frame. [self RB___setFrame:cache->rect withFraction:cache->fraction notify:YES]; } else { // Old state was collapsed, so we expand it. [self RB___finishExpand:cache->rect withFraction:cache->fraction]; } } else { // New state is collapsed. if (dim>=1.0) { // Old state was not collapsed, so we clear first responder and change the frame. [self RB___clearResponder]; [self RB___finishCollapse:cache->rect withFraction:dim/value]; } else { // It was collapsed already, but the frame may have changed, so we set it. [self RB___setFrame:cache->rect withFraction:cache->fraction notify:YES]; } } } // 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 tries to clear the first responder, if the current responder is a descendant of // the receiving subview. If so, it will set first responder to nil, redisplay the former responder and // return YES. Returns NO otherwise. - (BOOL)RB___clearResponder { NSWindow* window = [self window]; if (window) { NSView* responder = (NSView*)[window firstResponder]; if (responder&&[responder respondsToSelector:@selector(isDescendantOf:)]) { if ([responder isDescendantOf:self]) { if ([window makeFirstResponder:nil]) { [responder display]; return YES; } } } } return NO; } // 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]) { [self RB___clearResponder]; NSRect frame = [self frame]; BOOL ishor = [sv isHorizontal]; result = DIM(frame.size); // For collapsed views, fraction will contain the fraction of the dimension previously occupied DIM(frame.size) = 0.0; [self RB___finishCollapse:frame withFraction:result/[sv RB___dimensionWithoutDividers]]; } } return -result; } // This internal method finishes the collapse of a subview, stopping the animation if // there is one, and calling the delegate method if there is one. - (void)RB___finishCollapse:(NSRect)rect withFraction:(double)value { RBSplitView* sv = [self splitView]; BOOL finish = [self RB___stopAnimation]; [self RB___setFrame:rect withFraction:value notify:YES]; [sv RB___setMustClearFractions]; if (finish) { [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]) { NSRect frame = [super frame]; double frac = fraction; BOOL ishor = [sv isHorizontal]; if (setToMinimum) { result = DIM(frame.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(frame.size) = newdim; result = floorf(result); } [self RB___finishExpand:frame withFraction:0.0]; } 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:(NSRect)rect withFraction:(double)value { RBSplitView* sv = [self splitView]; BOOL finish = [self RB___stopAnimation]; [self RB___setFrame:rect withFraction:value notify:YES]; [sv RB___setMustClearFractions]; if (finish) { [self display]; } id delegate = [sv delegate]; if ([delegate respondsToSelector:@selector(splitView:didExpand:)]) { [delegate splitView:sv didExpand:self]; } } // These internal methods set 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 notify:(BOOL)notify { RBSplitView* sv = [self splitView]; id delegate = nil; if (notify) { delegate = [sv delegate]; // If the delegate method isn't implemented, we ignore the delegate altogether. if ([delegate respondsToSelector:@selector(splitView:changedFrameOfSubview:from:to:)]) { // If the rects are equal, the delegate isn't called. if (NSEqualRects(previous,rect)) { delegate = nil; } } else { delegate = nil; } } [sv setMustAdjust]; [self setFrame:rect]; fraction = value; [delegate splitView:sv changedFrameOfSubview:self from:previous to:rect]; previous = delegate?rect:NSZeroRect; } - (void)RB___setFrameSize:(NSSize)size withFraction:(double)value { [[self splitView] setMustAdjust]; [self setFrameSize:size]; fraction = value; } // This internal method gets the fraction value. - (double)RB___fraction { return fraction; } @end