// // 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 ((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)) { // 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 (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 (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