// // RBSplitView.m version 1.0.4 // RBSplitView // // Created by Rainer Brockerhoff on 24/09/2004. // Copyright 2004,2005 Rainer Brockerhoff. All rights reserved. // #import "RBSplitView.h" #import "RBSplitViewPrivateDefines.h" // Please don't remove this copyright notice! static const unsigned char RBSplitView_Copyright[] __attribute__ ((used)) = "RBSplitView Copyright(c)2004,2005 by Rainer Brockerhoff ."; @implementation RBSplitView // This class method clears the saved state(s) for a given autosave name from the defaults. + (void)removeStateUsingName:(NSString*)name { if ([name length]) { NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; [defaults removeObjectForKey:[[self class] defaultsKeyForName:name isHorizontal:NO]]; [defaults removeObjectForKey:[[self class] defaultsKeyForName:name isHorizontal:YES]]; } } // This class method returns the actual key used to store autosave data in the defaults. + (NSString*)defaultsKeyForName:(NSString*)name isHorizontal:(BOOL)orientation { return [NSString stringWithFormat:@"RBSplitView %@ %@",orientation?@"H":@"V",name]; } // This pair of methods gets and sets the autosave name, which allows restoring the subview's // state from the user defaults. // We take care not to allow nil autosaveNames. - (NSString*)autosaveName { return autosaveName; } // Sets the autosaveName; this should be a unique key to be used to store the subviews' proportions // in the user defaults. Default is @"", which doesn't save anything. Set flag to YES to set // unique names for nested subviews. You are responsible for avoiding duplicates; avoid using // the characters '[' and ']' in autosaveNames. - (void)setAutosaveName:(NSString*)aString recursively:(BOOL)flag { BOOL clear; if ((clear = ![aString length])) { aString = @""; } [RBSplitView removeStateUsingName:autosaveName]; [autosaveName autorelease]; autosaveName = [aString retain]; if (flag) { NSArray* subviews = [self subviews]; int subcount = [subviews count]; int i; for (i=0;i1)&&([[parts objectAtIndex:0] intValue]==subcount)&&(k==subcount)) { int i; NSRect frame = [self frame]; BOOL ishor = [self isHorizontal]; for (i=0;i0) { [self addSubview:[[[RBSplitSubview alloc] initWithFrame:frame] autorelease]]; } [self setMustAdjust]; } return self; } // Frees retained objects when going away. - (void)dealloc { if (dividers) { free(dividers); } [autosaveName release]; [divider release]; [background release]; [super dealloc]; } // Sets and gets the coupling between the view and its containing RBSplitView (if any). Coupled // RBSplitViews take some parameters, such as divider images, from the containing view. The default // is for nested RBSplitViews is YES; however, isCoupled returns NO if we're not nested. - (void)setCoupled:(BOOL)flag { if (flag!=isCoupled) { isCoupled = flag; // If we've just been uncoupled and there's no divider image, we copy it from the containing view. if (!isCoupled&&!divider) { [self setDivider:[[self splitView] divider]]; } [self setMustAdjust]; } } - (BOOL)isCoupled { return isCoupled&&([super splitView]!=nil); } // This returns the containing splitview if they are coupled. It's guaranteed to return a RBSplitView or nil. - (RBSplitView*)couplingSplitView { return isCoupled?[super couplingSplitView]:nil; } // This returns self. - (RBSplitView*)asSplitView { return self; } // This return self if we're really coupled to the owning splitview. - (RBSplitView*)coupledSplitView { return [self isCoupled]?self:nil; } // We always return NO, but do special handling in RBSplitSubview's mouseDown: method. - (BOOL)mouseDownCanMoveWindow { return NO; } // RBSplitViews must be flipped to work properly for horizontal dividers. As the subviews are never // flipped, this won't make your life harder. - (BOOL)isFlipped { return YES; } // Call this method to make sure that the subviews and divider rectangles are recalculated // properly before display. - (void)setMustAdjust { mustAdjust = YES; [super setNeedsDisplay:YES]; } // Returns YES if there's a pending adjustment. - (BOOL)mustAdjust { return mustAdjust; } // This pair of methods allows you to move the dividers for background windows while holding down // the command key, without bringing the window to the foreground. - (BOOL)acceptsFirstMouse:(NSEvent*)theEvent { return ([theEvent modifierFlags]&NSCommandKeyMask)==0; } - (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent*)theEvent { return ([theEvent modifierFlags]&NSCommandKeyMask)!=0; } // These 3 methods handle view background colors and opacity. The default is the window background. // Pass nil or a completely transparent color to setBackground to use transparency. If you set any // other background color, it will completely fill the RBSplitView (including subviews and dividers). // The view will be considered opaque only if its alpha is equal to 1.0. // For a nested, coupled RBSplitView, background and opacity are copied from the containing RBSplitView, // and setting the background has no effect. - (NSColor*)background { RBSplitView* sv = [self couplingSplitView]; return sv?[sv background]:background; } - (void)setBackground:(NSColor*)color { if (![self couplingSplitView]) { [background autorelease]; background = color?([color alphaComponent]>0.0?[color retain]:nil):nil; [self setNeedsDisplay:YES]; } } - (BOOL)isOpaque { RBSplitView* sv = [self couplingSplitView]; return sv?[sv isOpaque]:(background&&([background alphaComponent]==1.0)); } // This will make debugging a little easier by appending the state string to the // default description. - (NSString*)description { return [NSString stringWithFormat:@"%@ {%@}",[super description],[self stringWithSavedState]]; } // The following 3 methods handle divider orientation. The actual stored trait is horizontality, // but verticality is used for setting to conform to the NSSplitView convention. // For a nested RBSplitView, orientation is perpendicular to the containing RBSplitView, and // setting it has no effect. This parameter is not affected by coupling. // After changing the orientation you may want to restore the state with restoreState:. - (BOOL)isHorizontal { RBSplitView* sv = [self splitView]; return sv?[sv isVertical]:isHorizontal; } - (BOOL)isVertical { return 1-[self isHorizontal]; } - (void)setVertical:(BOOL)flag { if (![self splitView]&&(isHorizontal!=!flag)) { BOOL ishor = isHorizontal = !flag; NSSize size = divider?[divider size]:NSZeroSize; [self setDividerThickness:DIM(size)]; [self setMustAdjust]; } } // Returns the subview which a given identifier. - (RBSplitSubview*)subviewWithIdentifier:(NSString*)anIdentifier { NSEnumerator* enumerator = [[self subviews] objectEnumerator]; RBSplitSubview* subview; while ((subview = [enumerator nextObject])) { if ([anIdentifier isEqualToString:[subview identifier]]) { return subview; } } return nil; } // Returns the subview at a given position - (RBSplitSubview*)subviewAtPosition:(unsigned)position { NSArray* subviews = [self subviews]; int subcount = [subviews count]; if (position0.0) { return dividerThickness; } NSImage* div = [self divider]; if (div) { NSSize size = [div size]; BOOL ishor = [self isHorizontal]; return DIM(size); } return 0.0; } - (void)setDividerThickness:(float)thickness { float t = MAX(0.0,floorf(thickness)); if (dividerThickness!=t) { dividerThickness = t; [self setMustAdjust]; } } // These two methods add subviews. If aView isn't a RBSplitSubview, one is automatically inserted above // it, and aView's frame and resizing mask is set to occupy the entire RBSplitSubview. - (void)addSubview:(NSView*)aView { if ([aView isKindOfClass:[RBSplitSubview class]]) { [super addSubview:aView]; } else { [aView setFrameOrigin:NSZeroPoint]; RBSplitSubview* sub = [[[RBSplitSubview alloc] initWithFrame:[aView frame]] autorelease]; [aView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; [sub addSubview:aView]; [super addSubview:sub]; } [self setMustAdjust]; } - (void)addSubview:(NSView *)aView positioned:(NSWindowOrderingMode)place relativeTo:(NSView *)otherView { if ([aView isKindOfClass:[RBSplitSubview class]]) { [super addSubview:aView positioned:place relativeTo:otherView]; } else { [aView setFrameOrigin:NSZeroPoint]; RBSplitSubview* sub = [[[RBSplitSubview alloc] initWithFrame:[aView frame]] autorelease]; [aView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; [sub addSubview:aView]; [super addSubview:aView positioned:place relativeTo:otherView]; [aView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; } [self setMustAdjust]; } // This makes sure the subviews are adjusted after a subview is removed. - (void)willRemoveSubview:(NSView*)subview { if ([subview respondsToSelector:@selector(RB___stopAnimation)]) { [(RBSplitSubview*)subview RB___stopAnimation]; } [super willRemoveSubview:subview]; [self setMustAdjust]; } // RBSplitViews never resize their subviews automatically. - (BOOL)autoresizesSubviews { return NO; } // This adjusts the subviews when the size is set. setFrame: calls this, so all is well. It calls // the delegate if implemented. - (void)setFrameSize:(NSSize)size { NSSize oldsize = [self frame].size; if (!NSEqualSizes(size,oldsize)) { [super setFrameSize:size]; [self setMustAdjust]; if ([delegate respondsToSelector:@selector(splitView:wasResizedFrom:to:)]) { BOOL ishor = [self isHorizontal]; float olddim = DIM(oldsize); float newdim = DIM(size); // The delegate is not called if the dimension hasn't changed. if (newdim!=olddim) { [delegate splitView:self wasResizedFrom:olddim to:newdim]; } } // We adjust the subviews only if the delegate didn't. if (mustAdjust) { [self adjustSubviews]; } } } // This local function tries to expand the trailing subview. static void RBSVTryToExpandTrailing(RBSplitSubview* trailing,RBSplitSubview* leading,float delta) { // The mouse has to move over half of the expanded size (plus hysteresis) and the expansion shouldn't // reduce the leading subview to less than its minimum size. If it does, we try to collapse it first. // However, we don't collapse if that would cause the trailing subview to become larger than its maximum. float limit = [trailing minDimension]; float dimension = [leading dimension]; if (limit>dimension) { return; } limit += [leading minDimension]; if (limit>dimension) { if ([leading canCollapse]&&(-delta>(0.5+HYSTERESIS)*dimension)&&([trailing maxDimension]<=dimension)) { delta = -[leading RB___collapse]; [trailing changeDimensionBy:delta mayCollapse:NO]; } return; } // The trailing subview may be expanded normally. If it does expand, we shorten the leading subview. delta = -[trailing changeDimensionBy:-delta mayCollapse:NO]; [leading changeDimensionBy:delta mayCollapse:NO]; } // This local function tries to expand the leading subview (which is assumed to be collapsed). static void RBSVTryToExpandLeading(RBSplitSubview* trailing,RBSplitSubview* leading,float delta) { // The mouse has to move over half of the expanded size (plus hysteresis) and the expansion shouldn't // reduce the trailing subview to less than its minimum size. If it does, we try to collapse that first. // However, we don't collapse if that would cause the leading subview to become larger than its maximum. float limit = [leading minDimension]; float dimension = [trailing dimension]; if (limit>dimension) { return; } limit += [trailing minDimension]; if (limit>dimension) { if (([trailing canCollapse])&&(delta>(0.5+HYSTERESIS)*dimension)&&([leading maxDimension]<=dimension)) { delta = -[trailing RB___collapse]; [leading changeDimensionBy:delta mayCollapse:NO]; } return; } // The leading subview may be expanded normally. If it does expand, we shorten the trailing subview. delta = -[leading changeDimensionBy:delta mayCollapse:NO]; [trailing changeDimensionBy:delta mayCollapse:NO]; } // This local function tries to shorten the trailing subview. Both subviews are assumed to be expanded. // delta should be positive. If always is NO, the subview will be shortened only if it might also be // collapsed; otherwise, it's shortened as much as possible. static void RBSVTryToShortenTrailing(RBSplitSubview* trailing,RBSplitSubview* leading,float delta,BOOL always) { // We avoid making the leading subview larger than its maximum float limit = [leading maxDimension]-[leading dimension]; if (delta>limit) { if (always) { delta = limit; } else { return; } } BOOL ok = limit>=[trailing dimension]; if (always||ok) { delta = -[trailing changeDimensionBy:-delta mayCollapse:ok]; [leading changeDimensionBy:delta mayCollapse:NO]; } } // This local function tries to shorten the leading subview. Both subviews are assumed to be expanded. // delta should be negative. If always is NO, the subview will be shortened only if it might also be // collapsed; otherwise, it's shortened as much as possible. static void RBSVTryToShortenLeading(RBSplitSubview* trailing,RBSplitSubview* leading,float delta,BOOL always) { // We avoid making the trailing subview larger than its maximum float limit = [trailing maxDimension]-[trailing dimension]; if (-delta>limit) { if (always) { delta = -limit; } else { return; } } BOOL ok = limit>=[leading dimension]; if (always||ok) { delta = -[leading changeDimensionBy:delta mayCollapse:ok]; [trailing changeDimensionBy:delta mayCollapse:NO]; } } // This method handles dragging and double-clicking dividers with the mouse. While dragging, the // "closed hand" cursor is shown. Double clicks are handled separately. Nothing will happen if // no divider image is set. - (void)mouseDown:(NSEvent*)theEvent { if (!dividers) { return; } NSPoint where = [self convertPoint:[theEvent locationInWindow] fromView:nil]; NSArray* subviews = [self subviews]; int subcount = [subviews count]; if (subcount<2) { return; } // If there's no divider image, handle it in RBSplitSubview. if (![self divider]) { [super mouseDown:theEvent]; return; } BOOL ishor = [self isHorizontal]; int i; --subcount; // Loop over the divider rectangles until the mouse is within one. for (i=0;iorigin); // Check if the leading subview is nested and if yes, if one of its two-axis thumbs was hit. int ldiv = NSNotFound; float loffset = 0.0; NSPoint lwhere = where; NSRect lrect = NSZeroRect; if ((leading = [leading coupledSplitView])) { ldiv = [leading RB___dividerHitBy:lwhere relativeToView:self thickness:divt]; if (ldiv!=NSNotFound) { lrect = [leading RB___dividerRect:ldiv relativeToView:self]; loffset = OTHER(lwhere)-OTHER(lrect.origin); } } // Check if the trailing subview is nested and if yes, if one of its two-axis thumbs was hit. int tdiv = NSNotFound; float toffset = 0.0; NSPoint twhere = where; NSRect trect = NSZeroRect; if ((trailing = [trailing coupledSplitView])) { tdiv = [trailing RB___dividerHitBy:twhere relativeToView:self thickness:divt]; if (tdiv!=NSNotFound) { trect = [trailing RB___dividerRect:tdiv relativeToView:self]; toffset = OTHER(twhere)-OTHER(trect.origin); } } // Now we loop handling mouse events until we get a mouse up event, while showing the closed hand cursor. [[NSCursor closedHandCursor] push]; 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]; // Track the mouse along the main coordinate. [self RB___trackMouseEvent:theEvent from:where withBase:NSZeroPoint inDivider:i]; if (ldiv!=NSNotFound) { // Track any two-axis thumbs for the leading nested RBSplitView. [leading RB___trackMouseEvent:theEvent from:[self convertPoint:lwhere toView:leading] withBase:NSZeroPoint inDivider:ldiv]; } if (tdiv!=NSNotFound) { // Track any two-axis thumbs for the trailing nested RBSplitView. [trailing RB___trackMouseEvent:theEvent from:[self convertPoint:twhere toView:trailing] withBase:NSZeroPoint inDivider:tdiv]; } if (mustAdjust||[leading mustAdjust]||[trailing mustAdjust]) { // The mouse was dragged and the subviews changed, so we clear all fractions and adjust the subviews, as // several divider rectangles may have changed. RBSplitView* sv = [self splitView]; [sv?sv:self adjustSubviews]; // Display the changed view and adjust to the new cursor coordinate. [super display]; DIM(where) = DIM(div->origin)+offset; if ((ldiv!=NSNotFound)&&![leading isCollapsed]) { // Adjust for the leading nested RBSplitView's thumbs while it's not collapsed. lrect = [leading RB___dividerRect:ldiv relativeToView:self]; OTHER(lwhere) = OTHER(lrect.origin)+loffset; } if ((tdiv!=NSNotFound)&&![trailing isCollapsed]) { // Adjust for the trailing nested RBSplitView's thumbs while it's not collapsed. trect = [trailing RB___dividerRect:tdiv relativeToView:self]; OTHER(twhere) = OTHER(trect.origin)+toffset; } } [pool release]; } // Redisplay the previous cursor. [NSCursor pop]; } return; } } } // This will be called before the view will be redisplayed, so we adjust subviews if necessary. - (BOOL)needsDisplay { if (mustAdjust) { [self adjustSubviews]; return YES; } return [super needsDisplay]; } // We check if subviews must be adjusted before redisplaying programmatically. - (void)display { if (mustAdjust) { [self adjustSubviews]; } [super display]; } // This method draws the divider rectangles and then the two-axis thumbs if there are any. - (void)drawRect:(NSRect)rect { [super drawRect:rect]; if (!dividers) { return; } NSArray* subviews = [self subviews]; int subcount = [subviews count]; if (subcount<2) { return; } --subcount; int i; // Cache the divider image. NSImage* div = [self divider]; float divt = [self dividerThickness]; // Loop over the divider rectangles. for (i=0;i0.0) { // Positive delta means the mouse is being moved right or downwards. // firstTrailing will point at the first expanded subview to the right (or downwards) of the divider. // If there's none (all subviews are collapsed) it will point at the nearest subview. RBSplitSubview* firstTrailing = trailing; k = index+1; while ([firstTrailing isCollapsed]) { if (++k>=subcount) { firstTrailing = trailing; break; } firstTrailing = [subviews objectAtIndex:k]; } // If the leading subview is collapsed, it might be expanded if some conditions are met. if ([leading isCollapsed]) { RBSVTryToExpandLeading(firstTrailing,leading,delta); } else { // The leading subview is not collapsed, so we try to shorten or even collapse it RBSVTryToShortenTrailing(firstTrailing,leading,delta,YES); } } } // This is called for nested RBSplitViews, to add the cursor rects for the two-axis thumbs. - (void)RB___addCursorRectsTo:(RBSplitView*)masterView forDividerRect:(NSRect)rect thickness:(float)delta { if ([self divider]) { NSArray* subviews = [self subviews]; int divcount = [subviews count]-1; if (divcount<1) { return; } int i; NSCursor* cursor = [NSCursor openHandCursor]; BOOL ishor = [self isHorizontal]; // Loop over the divider rectangles, intersect them with the view's own, and add the thumb rectangle // to the containing split view. for (i=0;iconstrain = NO; if (curr->size>0.0) { expsize += curr->size+curr->fraction; haveexp = YES; } else { limit = [curr->sub minDimension]; if (smalldim>limit) { smalldim = limit; smallest = i; } } } // haveexp should be YES at this point. If not, all subviews were collapsed; can't have that, so we // expand the smallest subview (or the first, if all have the same minimum). curr = &caches[smallest]; if (!haveexp) { curr->size = [curr->sub minDimension]; curr->fraction = 0.0; expsize += curr->size; } // If the total dimension of all expanded subviews is less than 1.0 we set the dimension of the smallest // subview (which we're sure is expanded at this point) to the available space. newsize = DIM(bounds.size)-divcount*divt; if (expsize<1.0) { curr->size = newsize; curr->fraction = 0.0; expsize = newsize; } // Loop over the subviews and check if they're within the limits after scaling. We also recalculate the // exposed size and repeat until no more subviews hit the constraints during that loop. BOOL constrained; effsize = newsize;// we're caching newsize here, this is an integer. do { // scale is the scalefactor by which all views should be scaled - assuming none have constraints. // It's a double to (hopefully) keep rounding errors small enough for all practical purposes. double scale = newsize/expsize; constrained = NO; realsize = 0.0; expsize = 0.0; for (i=0;isize>0.0) { // Check non-collapsed subviews only. if (!curr->constrain) { // Check non-constrained subviews only; calculate the proposed new size. float cursize = (curr->size+curr->fraction)*scale; // Check if we hit a limit. limit will contain either the max or min dimension, whichever was hit. if (([curr->sub RB___animationData:NO]&&((limit = curr->size)>0.0))|| (cursize<(limit = [curr->sub minDimension]))|| (cursize>(limit = [curr->sub maxDimension]))) { // If we hit a limit, we mark the view and set to repeat the loop; non-constrained subviews will // have to be recalculated. curr->constrain = constrained = YES; // We set the new size to the limit we hit, and subtract it from the total size to be subdivided. cursize = limit; curr->fraction = 0.0; newsize -= cursize; } else { // If we didn't hit a limit, we round the size to the nearest integer and recalculate the fraction. double rem = fmod(cursize,1.0); cursize -= rem; if (rem>0.5) { ++cursize; --rem; } expsize += cursize; curr->fraction = rem; } // We store the new size in the cache. curr->size = cursize; } // And add the full size with fraction to the actual sum of all expanded subviews. realsize += curr->size+curr->fraction; } } // At this point, newsize will be the sum of the new dimensions of non-constrained views. // expsize will be the sum of the recalculated dimensions of the same views, if any. // We repeat the loop if any view has been recently constrained, and if there are any // unconstrained views left. } while (constrained&&(expsize>0.0)); // At this point, the difference between realsize and effsize should be less than 1 pixel. // realsize is the total size of expanded subviews as recalculated above, and // effsize is the value realsize should have. limit = realsize-effsize; if (limit>=1.0) { // If realsize is larger than effsize by 1 pixel or more, we will need to collapse subviews to make room. // This in turn might expand previously collapsed subviews. So, we'll try collapsing constrained subviews // until we're back into range, and then recalculate everything from the beginning. for (i=0;iconstrain)&&[curr->sub canCollapse]) { realsize -= curr->size; if (realsize<1.0) { break; } curr->size = 0.0; if ((realsize-effsize)<1.0) { break; } } } } else if (limit<=-1.0) { // If realsize is smaller than effsize by 1 pixel or more, we will need to expand subviews. // This in turn might collapse previously expanded subviews. So, we'll try expanding collapsed subviews // until we're back into range, and then recalculate everything from the beginning. for (i=0;isize<=0.0) { curr->size = [curr->sub minDimension]; curr->fraction = 0.0; realsize += curr->size; if ((realsize-effsize)>-1.0) { break; } } } } else { // The difference is less than 1 pixel, meaning that in all probability our calculations are // exact or off by at most one pixel after rounding, so we break the loop here. break; } } // After passing through the outer loop twice, the frames may still be wrong, but there's nothing // else we can do about it. You probably should avoid this by some other means like setting a minimum // or maximum size for the window, for instance, or leaving at least one unlimited subview. // newframe is used to reset all subview frames. Subviews always fill the entire RBSplitView along the // current orientation. NSRect newframe = NSMakeRect(0.0,0.0,bounds.size.width,bounds.size.height); // We now loop over the subviews yet again and set the definite frames, also recalculating the // divider rectangles as we go along, and collapsing and expanding subviews whenever requested. RBSplitSubview* last = nil; // And we make a note if there's any nested RBSplitView. int nested = NSNotFound; newsize = DIM(bounds.size)-divcount*divt; for (i=0;isub asSplitView]!=nil)) { nested = i; } // Adjust the subview to the correct origin and resize it to fit into the "other" dimension. curr->rect.origin = newframe.origin; OTHER(curr->rect.size) = OTHER(newframe.size); DIM(curr->rect.size) = curr->size; // Clear fractions for expanded subviews if requested. if ((curr->size>0.0)&&mustClearFractions) { curr->fraction = 0.0; } // Ask the subview to do the actual moving/resizing etc. from the cache. [curr->sub RB___updateFromCache:curr withTotalDimension:effsize]; // Step to the next position and record the subview if it's not collapsed. DIM(newframe.origin) += curr->size; if (curr->size>0.0) { last = curr->sub; } if (i==divcount) { // We're at the last subview, so we now check if the actual and calculated dimensions // are the same. float remain = DIM(bounds.size)-DIM(newframe.origin); if (last&&(remain!=0.0)) { // We'll resize the last expanded subview to whatever it takes to squeeze within the frame. // Normally the change should be at most one pixel, but if too many subviews were constrained, // this may be a large value, and the last subview may be resized beyond its constraints; // there's nothing else to do at this point. newframe = [last frame]; DIM(newframe.size) += remain; [last RB___setFrameSize:newframe.size withFraction:[last RB___fraction]-remain]; // And we loop back over the rightmost dividers (if any) to adjust their offsets. while (last!=[subviews objectAtIndex:i]) { DIM(dividers[--i].origin) += remain; } break; } } else { // For any but the last subview, we just calculate the divider frame. DIM(newframe.size) = divt; dividers[i] = newframe; DIM(newframe.origin) += divt; } } // If there was at least one nested RBSplitView, we loop over the subviews and adjust those. for (i=nested;isub asSplitView] adjustSubviews]; } // Free the cache array. free(caches); } // Clear flags and cursor rects. mustAdjust = NO; mustClearFractions = NO; [[self window] invalidateCursorRectsForView:self]; // Save the state for all subviews. [self saveState:YES]; // If we're a nested RBSplitView, also invalidate cursorRects for the superview. RBSplitView* sv = [self couplingSplitView]; if (sv) { [[self window] invalidateCursorRectsForView:sv]; } } @end