Fix problems with 'fullscreen' and :mksession
[MacVim.git] / src / MacVim / RBSplitView.m
blob057645a91ad7e3ccc69b644f37d83442cd6ae8cd
1 //
2 //  RBSplitView.m version 1.1.4
3 //  RBSplitView
4 //
5 //  Created by Rainer Brockerhoff on 24/09/2004.
6 //  Copyright 2004-2006 Rainer Brockerhoff.
7 //      Some Rights Reserved under the Creative Commons Attribution License, version 2.5, and/or the MIT License.
8 //
10 #import "RBSplitView.h"
11 #import "RBSplitViewPrivateDefines.h"
13 // Please don't remove this copyright notice!
14 static const unsigned char RBSplitView_Copyright[] __attribute__ ((used)) =
15         "RBSplitView 1.1.4 Copyright(c)2004-2006 by Rainer Brockerhoff <rainer@brockerhoff.net>.";
17 // This vector keeps currently used cursors. nil means the default cursor.
18 static NSCursor* cursors[RBSVCursorTypeCount] = {nil};
20 // Our own fMIN and fMAX
21 static inline float fMIN(float a,float b) {
22         return a<b?a:b;
25 static inline float fMAX(float a,float b) {
26         return a>b?a:b;
29 @implementation RBSplitView
31 // These class methods get and set the cursor used for each type.
32 // Pass in nil to reset to the default cursor for that type.
33 + (NSCursor*)cursor:(RBSVCursorType)type {
34         if ((type>=0)&&(type<RBSVCursorTypeCount)) {
35                 NSCursor* result = cursors[type];
36                 if (result) {
37                         return result;
38                 }
39                 switch (type) {
40                         case RBSVHorizontalCursor:
41                                 return [NSCursor resizeUpDownCursor];
42                         case RBSVVerticalCursor:
43                                 return [NSCursor resizeLeftRightCursor];
44                         case RBSV2WayCursor:
45                                 return [NSCursor openHandCursor];
46                         case RBSVDragCursor:
47                                 return [NSCursor closedHandCursor];
48                         default:
49                                 break;
50                 }
51         }
52         return [NSCursor currentCursor];
55 + (void)setCursor:(RBSVCursorType)type toCursor:(NSCursor*)cursor {
56         if ((type>=0)&&(type<RBSVCursorTypeCount)) {
57                 [cursors[type] release];
58                 cursors[type] = [cursor retain];
59         }
62 // This class method clears the saved state(s) for a given autosave name from the defaults.
63 + (void)removeStateUsingName:(NSString*)name {
64         if ([name length]) {
65                 NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
66                 [defaults removeObjectForKey:[[self class] defaultsKeyForName:name isHorizontal:NO]];
67                 [defaults removeObjectForKey:[[self class] defaultsKeyForName:name isHorizontal:YES]];
68         }
71 // This class method returns the actual key used to store autosave data in the defaults.
72 + (NSString*)defaultsKeyForName:(NSString*)name isHorizontal:(BOOL)orientation {
73     return [NSString stringWithFormat:@"RBSplitView %@ %@",orientation?@"H":@"V",name];
76 // This pair of methods gets and sets the autosave name, which allows restoring the subview's
77 // state from the user defaults.
78 // We take care not to allow nil autosaveNames.
79 - (NSString*)autosaveName {
80         return autosaveName;
83 // Sets the autosaveName; this should be a unique key to be used to store the subviews' proportions
84 // in the user defaults. Default is @"", which doesn't save anything. Set flag to YES to set
85 // unique names for nested subviews. You are responsible for avoiding duplicates; avoid using
86 // the characters '[' and ']' in autosaveNames.
87 - (void)setAutosaveName:(NSString*)aString recursively:(BOOL)flag {
88         BOOL clear;
89         if ((clear = ![aString length])) {
90                 aString = @"";
91         }
92         [RBSplitView removeStateUsingName:autosaveName];
93         [autosaveName autorelease];
94         autosaveName = [aString retain];
95         if (flag) {
96                 NSArray* subviews = [self subviews];
97                 int subcount = [subviews count];
98                 int i;
99                 for (i=0;i<subcount;i++) {
100                         RBSplitView* sv = [[subviews objectAtIndex:i] asSplitView];
101                         if (sv) {
102                                 NSString* subst = clear?@"":[aString stringByAppendingFormat:@"[%d]",i];
103                                 [sv setAutosaveName:subst recursively:YES];
104                         }
105                 }
106         }
109 // Saves the current state of the subviews if there's a valid autosave name set. If the argument
110 // is YES, it's then also called recursively for nested RBSplitViews. Returns YES if successful.
111 // You must call restoreState explicity at least once before saveState will begin working.
112 - (BOOL)saveState:(BOOL)recurse {
113 // Saving the state is also disabled while dragging.
114         if (canSaveState&&![self isDragging]&&[autosaveName length]) {
115                 [[NSUserDefaults standardUserDefaults] setObject:[self stringWithSavedState] forKey:[[self class] defaultsKeyForName:autosaveName isHorizontal:[self isHorizontal]]];
116                 if (recurse) {
117                         NSEnumerator* enumerator = [[self subviews] objectEnumerator];
118                         RBSplitSubview* sub;
119                         while ((sub = [enumerator nextObject])) {
120                                 [[sub asSplitView] saveState:YES];
121                         }
122                 }
123                 return YES;
124         }
125         return NO;
128 // Restores the saved state of the subviews if there's a valid autosave name set. If the argument
129 // is YES, it's also called recursively for nested RBSplitViews. Returns YES if successful.
130 // It's good policy to call adjustSubviews immediately after calling restoreState.
131 - (BOOL)restoreState:(BOOL)recurse {
132         BOOL result = NO;
133         if ([autosaveName length]) {
134                 result = [self setStateFromString:[[NSUserDefaults standardUserDefaults] stringForKey:[[self class] defaultsKeyForName:autosaveName isHorizontal:[self isHorizontal]]]];
135                 if (result&&recurse) {
136                         NSEnumerator* enumerator = [[self subviews] objectEnumerator];
137                         RBSplitSubview* sub;
138                         while ((sub = [enumerator nextObject])) {
139                                 [[sub asSplitView] restoreState:YES];
140                         }
141                 }
142         }
143         canSaveState = YES;
144         return result;
147 // Returns an array with complete state information for the receiver and all subviews, taking
148 // nesting into account. Don't store this array in a file, as its format might change in the
149 // future; this is for taking a state snapshot and later restoring it with setStatesFromArray.
150 - (NSArray*)arrayWithStates {
151         NSMutableArray* array = [NSMutableArray array];
152         [array addObject:[self stringWithSavedState]];
153         NSEnumerator* enumerator = [[self subviews] objectEnumerator];
154         RBSplitSubview* sub;
155         while ((sub = [enumerator nextObject])) {
156                 RBSplitView* suv = [sub asSplitView];
157                 if (suv) {
158                         [array addObject:[suv arrayWithStates]];
159                 } else {
160                         [array addObject:[NSNull null]];
161                 }
162         }
163         return array;
166 // Restores the state of the receiver and all subviews. The array must have been produced by a
167 // previous call to arrayWithStates. Returns YES if successful. This will fail if you have
168 // added or removed subviews in the meantime!
169 // You need to call adjustSubviews after calling this.
170 - (BOOL)setStatesFromArray:(NSArray*)array {
171         NSArray* subviews = [self subviews];
172         unsigned int count = [array count];
173         if (count==([subviews count]+1)) {
174                 NSString* me = [array objectAtIndex:0];
175                 if ([me isKindOfClass:[NSString class]]) {
176                         if ([self setStateFromString:me]) {
177                                 unsigned int i;
178                                 for (i=1;i<count;i++) {
179                                         NSArray* item = [array objectAtIndex:i];
180                                         RBSplitView* suv = [[subviews objectAtIndex:i-1] asSplitView];
181                                         if ([item isKindOfClass:[NSArray class]]==(suv!=nil)) {
182                                                 if (suv&&![suv setStatesFromArray:item]) {
183                                                         return NO;
184                                                 }
185                                         } else {
186                                                 return NO;
187                                         }
188                                 }
189                                 return YES;
190                         }
191                 }
192         }
193         return NO;
196 // Returns a string encoding the current state of all direct subviews. Does not check for nesting.
197 // The string contains the number of direct subviews, then the dimension for each subview (which will
198 // be negative for collapsed subviews), all separated by blanks.
199 - (NSString*)stringWithSavedState {
200         NSArray* subviews = [self subviews];
201         NSMutableString* result = [NSMutableString stringWithFormat:@"%d",[subviews count]];
202         NSEnumerator* enumerator = [subviews objectEnumerator];
203         RBSplitSubview* sub;
204         while ((sub = [enumerator nextObject])) {
205                 double size = [sub dimension];
206                 if ([sub isCollapsed]) {
207                         size = -size;
208                 } else {
209                         size += +[sub RB___fraction];
210                 }
211                 [result appendFormat:[sub isHidden]?@" %gH":@" %g",size];
212         }
213         return result;
216 // Readjusts all direct subviews according to the encoded string parameter.
217 // The number of subviews must match. Returns YES if successful. Does not check for nesting.
218 - (BOOL)setStateFromString:(NSString*)aString {
219         if ([aString length]) {
220                 NSArray* parts = [aString componentsSeparatedByString:@" "];
221                 NSArray* subviews = [self subviews];
222                 int subcount = [subviews count];
223                 int k = [parts count];
224                 if ((k-->1)&&([[parts objectAtIndex:0] intValue]==subcount)&&(k==subcount)) {
225                         int i;
226                         NSRect frame = [self frame];
227                         BOOL ishor = [self isHorizontal];
228                         for (i=0;i<subcount;i++) {
229                                 NSString* part = [parts objectAtIndex:i+1];
230                                 BOOL hidden = [part hasSuffix:@"H"];
231                                 double size = [part doubleValue];
232                                 BOOL negative = size<=0.0;
233                                 if (negative) {
234                                         size = -size;
235                                 }
236                                 double fract = size;
237                                 size = floorf(size);
238                                 fract -= size;
239                                 DIM(frame.size) = size;
240                                 RBSplitSubview* sub = [subviews objectAtIndex:i];
241                                 [sub RB___setFrame:frame withFraction:fract notify:NO];
242                                 if (negative) {
243                                         [sub RB___collapse];
244                                 }
245                                 [sub RB___setHidden:hidden];
246                         }
247                         [self setMustAdjust];
248                         return YES;
249                 }
250         }
251         return NO;
254 // This is the designated initializer for creating RBSplitViews programmatically. You can set the
255 // divider image and other parameters afterwards.
256 - (id)initWithFrame:(NSRect)frame {
257     self = [super initWithFrame:frame];
258     if (self) {
259                 dividers = NULL;
260                 isCoupled = YES;
261                 isDragging = NO;
262                 isInScrollView = NO;
263                 canSaveState = NO;
264                 [self setVertical:YES];
265                 [self setDivider:nil];
266                 [self setAutosaveName:nil recursively:NO];
267                 [self setBackground:nil];
268         }
269         return self;
272 // This convenience initializer adds any number of subviews and adjusts them proportionally.
273 - (id)initWithFrame:(NSRect)frame andSubviews:(unsigned)count {
274         self = [self initWithFrame:frame];
275         if (self) {
276                 while (count-->0) {
277                         [self addSubview:[[[RBSplitSubview alloc] initWithFrame:frame] autorelease]];
278                 }
279                 [self setMustAdjust];
280         }
281         return self;
284 // Frees retained objects when going away.
285 - (void)dealloc {
286         if (dividers) {
287                 free(dividers);
288         }
289         [autosaveName release];
290         [divider release];
291         [background release];
292         [super dealloc];
295 // Sets and gets the coupling between the view and its containing RBSplitView (if any). Coupled
296 // RBSplitViews take some parameters, such as divider images, from the containing view. The default
297 // is for nested RBSplitViews is YES; however, isCoupled returns NO if we're not nested.
298 - (void)setCoupled:(BOOL)flag {
299         if (flag!=isCoupled) {
300                 isCoupled = flag;
301 // If we've just been uncoupled and there's no divider image, we copy it from the containing view. 
302                 if (!isCoupled&&!divider) {
303                         [self setDivider:[[self splitView] divider]];
304                 }
305                 [self setMustAdjust];
306         }
309 - (BOOL)isCoupled {
310         return isCoupled&&([super splitView]!=nil);
313 // This returns the containing splitview if they are coupled. It's guaranteed to return a RBSplitView or nil.
314 - (RBSplitView*)couplingSplitView {
315         return isCoupled?[super couplingSplitView]:nil;
318 // This returns self.
319 - (RBSplitView*)asSplitView {
320         return self;
323 // This return self if we're really coupled to the owning splitview.
324 - (RBSplitView*)coupledSplitView {
325         return [self isCoupled]?self:nil;
328 // We always return NO, but do special handling in RBSplitSubview's mouseDown: method.
329 - (BOOL)mouseDownCanMoveWindow {
330         return NO;
333 // RBSplitViews must be flipped to work properly for horizontal dividers. As the subviews are never
334 // flipped, this won't make your life harder.
335 - (BOOL)isFlipped {
336         return YES;
339 // Call this method to make sure that the subviews and divider rectangles are recalculated
340 // properly before display.
341 - (void)setMustAdjust {
342         mustAdjust = YES;
343         [self setNeedsDisplay:YES];
346 // Returns YES if there's a pending adjustment.
347 - (BOOL)mustAdjust {
348         return mustAdjust;
351 // Returns YES if we're in a dragging loop.
352 - (BOOL)isDragging {
353         return isDragging;
356 // Returns YES if the view is directly contained in an NSScrollView.
357 - (BOOL)isInScrollView {
358         return isInScrollView;
361 // This pair of methods allows you to move the dividers for background windows while holding down
362 // the command key, without bringing the window to the foreground.
363 - (BOOL)acceptsFirstMouse:(NSEvent*)theEvent {
364         return ([theEvent modifierFlags]&NSCommandKeyMask)==0;
367 - (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent*)theEvent {
368         return ([theEvent modifierFlags]&NSCommandKeyMask)!=0;
371 // These 3 methods handle view background colors and opacity. The default is the window background.
372 // Pass nil or a completely transparent color to setBackground to use transparency. If you set any
373 // other background color, it will completely fill the RBSplitView (including subviews and dividers).
374 // The view will be considered opaque only if its alpha is equal to 1.0.
375 // For a nested, coupled RBSplitView, background and opacity are copied from the containing RBSplitView,
376 // and setting the background has no effect.
377 - (NSColor*)background {
378         RBSplitView* sv = [self couplingSplitView];
379         return sv?[sv background]:background;
382 - (void)setBackground:(NSColor*)color {
383         if (![self couplingSplitView]) {
384                 [background autorelease];
385                 background = color?([color alphaComponent]>0.0?[color retain]:nil):nil;
386                 [self setNeedsDisplay:YES];
387         }
390 - (BOOL)isOpaque {
391         RBSplitView* sv = [self couplingSplitView];
392         return sv?[sv isOpaque]:(background&&([background alphaComponent]>=1.0));
395 // This will make debugging a little easier by appending the state string to the
396 // default description.
397 - (NSString*)description {
398         return [NSString stringWithFormat:@"%@ {%@}",[super description],[self stringWithSavedState]];
401 // The following 3 methods handle divider orientation. The actual stored trait is horizontality,
402 // but verticality is used for setting to conform to the NSSplitView convention.
403 // For a nested RBSplitView, orientation is perpendicular to the containing RBSplitView, and
404 // setting it has no effect. This parameter is not affected by coupling.
405 // After changing the orientation you may want to restore the state with restoreState:.
406 - (BOOL)isHorizontal {
407         RBSplitView* sv = [self splitView];
408         return sv?[sv isVertical]:isHorizontal;
411 - (BOOL)isVertical {
412         return 1-[self isHorizontal];
415 - (void)setVertical:(BOOL)flag {
416         if (![self splitView]&&(isHorizontal!=!flag)) {
417                 BOOL ishor = isHorizontal = !flag;
418                 NSSize size = divider?[divider size]:NSZeroSize;
419                 [self setDividerThickness:DIM(size)];
420                 [self setMustAdjust];
421         }
424 // Returns the subview which a given identifier.
425 - (RBSplitSubview*)subviewWithIdentifier:(NSString*)anIdentifier {
426         NSEnumerator* enumerator = [[self subviews] objectEnumerator];
427         RBSplitSubview* subview;
428         while ((subview = [enumerator nextObject])) {
429                 if ([anIdentifier isEqualToString:[subview identifier]]) {
430                         return subview;
431                 }
432         }
433         return nil;
436 // Returns the subview at a given position
437 - (RBSplitSubview*)subviewAtPosition:(unsigned)position {
438         NSArray* subviews = [super subviews];
439         unsigned int subcount = [subviews count];
440         if (position<subcount) {
441                 return [subviews objectAtIndex:position];
442         }
443         return nil;
446 // This pair of methods gets and sets the delegate object. Delegates aren't retained.
447 - (id)delegate {
448         return delegate;
451 - (void)setDelegate:(id)anObject {
452         delegate = anObject;
455 // This pair of methods gets and sets the divider image. Setting the image automatically adjusts the
456 // divider thickness. A nil image means a 0-pixel wide divider, unless you set a thickness explicitly.
457 // For a nested RBSplitView, the divider is copied from the containing RBSplitView, and
458 // setting it has no effect. The returned image is always flipped.
459 - (NSImage*)divider {
460         RBSplitView* sv = [self couplingSplitView];
461         return sv?[sv divider]:divider;
464 - (void)setDivider:(NSImage*)image {
465         if (![self couplingSplitView]) {
466                 [divider autorelease];
467                 if ([image isFlipped]) {
468 // If the image is flipped, we just retain it.
469                         divider = [image retain];
470                 } else {
471 // if the image isn't flipped, we copy the image instead of retaining it, and flip it.
472                         divider = [image copy];
473                         [divider setFlipped:YES];
474                 }
475 // We set the thickness to 0.0 so the image dimension will prevail.
476                 [self setDividerThickness:0.0];
477                 [self setMustAdjust];
478         }
481 // This pair of methods gets and sets the divider thickness. It should be an integer value and at least
482 // 0.0, so we make sure. Set it to 0.0 to make the image dimensions prevail.
483 - (float)dividerThickness {
484         if (dividerThickness>0.0) {
485                 return dividerThickness;
486         }
487         NSImage* divdr = [self divider];
488         if (divdr) {
489                 NSSize size = [divdr size];
490                 BOOL ishor = [self isHorizontal];
491                 return DIM(size);
492         }
493         return 0.0;
496 - (void)setDividerThickness:(float)thickness {
497         float t = fMAX(0.0,floorf(thickness));
498         if ((int)dividerThickness!=(int)t) {
499                 dividerThickness = t;
500                 [self setMustAdjust];
501         }
504 // These three methods add subviews. If aView isn't a RBSplitSubview, one is automatically inserted above
505 // it, and aView's frame and resizing mask is set to occupy the entire RBSplitSubview.
506 - (void)addSubview:(NSView*)aView {
507         if ([aView isKindOfClass:[RBSplitSubview class]]) {
508                 [super addSubview:aView];
509         } else {
510                 [aView setFrameOrigin:NSZeroPoint];
511                 RBSplitSubview* sub = [[[RBSplitSubview alloc] initWithFrame:[aView frame]] autorelease];
512                 [aView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
513                 [sub addSubview:aView];
514                 [super addSubview:sub];
515         }
516         [self setMustAdjust];
519 - (void)addSubview:(NSView*)aView positioned:(NSWindowOrderingMode)place relativeTo:(NSView*)otherView {
520         if ([aView isKindOfClass:[RBSplitSubview class]]) {
521                 [super addSubview:aView positioned:place relativeTo:otherView];
522         } else {
523                 [aView setFrameOrigin:NSZeroPoint];
524                 RBSplitSubview* sub = [[[RBSplitSubview alloc] initWithFrame:[aView frame]] autorelease];
525                 [aView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
526                 [sub addSubview:aView];
527                 [super addSubview:sub positioned:place relativeTo:otherView];
528                 [aView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
529         }
530         [self setMustAdjust];
533 - (void)addSubview:(NSView*)aView atPosition:(unsigned)position {
534         RBSplitSubview* suv = [self subviewAtPosition:position];
535         if (suv) {
536                 [self addSubview:aView positioned:NSWindowBelow relativeTo:suv];
537         } else {
538                 [self addSubview:aView];
539         }
542 // This keeps the isInScrollView flag up-to-date.
543 - (void)viewDidMoveToSuperview {
544         [super viewDidMoveToSuperview];
545         NSScrollView* scrollv = [self enclosingScrollView];
546         isInScrollView = scrollv?[scrollv documentView]==self:NO;
549 // This makes sure the subviews are adjusted after a subview is removed.
550 - (void)willRemoveSubview:(NSView*)subview {
551         if ([subview respondsToSelector:@selector(RB___stopAnimation)]) {
552                 [(RBSplitSubview*)subview RB___stopAnimation];
553         }
554         [super willRemoveSubview:subview];
555         [self setMustAdjust];
558 // RBSplitViews never resize their subviews automatically.
559 - (BOOL)autoresizesSubviews {
560         return NO;
563 // This adjusts the subviews when the size is set. setFrame: calls this, so all is well. It calls
564 // the delegate if implemented.
565 - (void)setFrameSize:(NSSize)size {
566         NSSize oldsize = [self frame].size;
567         [super setFrameSize:size];
568         [self setMustAdjust];
569         if ([delegate respondsToSelector:@selector(splitView:wasResizedFrom:to:)]) {
570                 BOOL ishor = [self isHorizontal];
571                 float olddim = DIM(oldsize);
572                 float newdim = DIM(size);
573 // The delegate is not called if the dimension hasn't changed.
574                 if (((int)newdim!=(int)olddim)) {
575                         [delegate splitView:self wasResizedFrom:olddim to:newdim];
576                 }
577         }
578 // We adjust the subviews only if the delegate didn't.
579         if (mustAdjust&&!isAdjusting) {
580                 [self adjustSubviews];
581         }
584 // This method handles dragging and double-clicking dividers with the mouse. While dragging, the
585 // "closed hand" cursor is shown. Double clicks are handled separately. Nothing will happen if
586 // no divider image is set.
587 - (void)mouseDown:(NSEvent*)theEvent {
588         if (!dividers) {
589                 return;
590         }
591         NSArray* subviews = [self RB___subviews];
592         int subcount = [subviews count];
593         if (subcount<2) {
594                 return;
595         }
596 // If the mousedown was in an alternate dragview, or if there's no divider image, handle it in RBSplitSubview.
597         if ((actDivider<NSNotFound)||![self divider]) {
598                 [super mouseDown:theEvent];
599                 return;
600         }
601         NSPoint where = [self convertPoint:[theEvent locationInWindow] fromView:nil];
602         BOOL ishor = [self isHorizontal];
603         int i;
604         --subcount;
605 // Loop over the divider rectangles.
606         for (i=0;i<subcount;i++) {
607                 NSRect* divdr = &dividers[i];
608                 if ([self mouse:where inRect:*divdr]) {
609 // leading points at the subview immediately leading the divider being tracked.
610                         RBSplitView* leading = [subviews objectAtIndex:i];
611 // trailing points at the subview immediately trailing the divider being tracked.
612                         RBSplitView* trailing = [subviews objectAtIndex:i+1];
613                         if ([delegate respondsToSelector:@selector(splitView:shouldHandleEvent:inDivider:betweenView:andView:)]) {
614                                 if (![delegate splitView:self shouldHandleEvent:theEvent inDivider:i betweenView:leading andView:trailing]) {
615                                         return;
616                                 }
617                         }
618 // If it's a double click, try to expand or collapse one of the neighboring subviews.
619                         if ([theEvent clickCount]>1) {
620 // If both are collapsed, we do nothing. If one of them is collapsed, we try to expand it.
621                                 if ([trailing isCollapsed]) {
622                                         if (![leading isCollapsed]) {
623                                                 [self RB___tryToExpandTrailing:trailing leading:leading delta:-[trailing dimension]];
624                                         }
625                                 } else {
626                                         if ([leading isCollapsed]) {
627                                                 [self RB___tryToExpandLeading:leading divider:i trailing:trailing delta:[leading dimension]];
628                                         } else {
629 // If neither are collapsed, we check if both are collapsible.
630                                                 BOOL lcan = [leading canCollapse];
631                                                 BOOL tcan = [trailing canCollapse];
632                                                 float ldim = [leading dimension];
633                                                 if (lcan&&tcan) {
634 // If both are collapsible, we try asking the delegate.
635                                                         if ([delegate respondsToSelector:@selector(splitView:collapseLeading:orTrailing:)]) {
636                                                                 RBSplitSubview* sub = [delegate splitView:self collapseLeading:leading orTrailing:trailing];
637 // If the delegate returns nil, neither view will collapse.
638                                                                 lcan = sub==leading;
639                                                                 tcan = sub==trailing;
640                                                         } else {
641 // Otherwise we try collapsing the smaller one. If they're equal, the trailing one will be collapsed.
642                                                                 lcan = ldim<[trailing dimension];
643                                                         }
644                                                 }
645 // At this point, we'll try to collapse the leading subview.
646                                                 if (lcan) {
647                                                         [self RB___tryToShortenLeading:leading divider:i trailing:trailing delta:-ldim always:NO];
648                                                 }
649 // If the leading subview didn't collapse for some reason, we try to collapse the trailing one.
650                                                 if (!mustAdjust&&tcan) {
651                                                         [self RB___tryToShortenTrailing:trailing divider:i leading:leading delta:[trailing dimension] always:NO];
652                                                 }
653                                         }
654                                 }
655 // If the subviews have changed, clear the fractions, adjust and redisplay
656                                 if (mustAdjust) {
657                                         [self RB___setMustClearFractions];
658                                         RBSplitView* sv = [self splitView];
659                                         [sv?sv:self adjustSubviews];
660                                         [super display];
661                                 }
662                         } else {
663 // Single click; record the offsets within the divider rectangle and check for nesting.
664                                 float divt = [self dividerThickness];
665                                 float offset = DIM(where)-DIM(divdr->origin);
666 // Check if the leading subview is nested and if yes, if one of its two-axis thumbs was hit.
667                                 int ldivdr = NSNotFound;
668                                 float loffset = 0.0;
669                                 NSPoint lwhere = where;
670                                 NSRect lrect = NSZeroRect;
671                                 if ((leading = [leading coupledSplitView])) {
672                                         ldivdr = [leading RB___dividerHitBy:lwhere relativeToView:self thickness:divt];
673                                         if (ldivdr!=NSNotFound) {
674                                                 lrect = [leading RB___dividerRect:ldivdr relativeToView:self];
675                                                 loffset = OTHER(lwhere)-OTHER(lrect.origin);
676                                         }
677                                 }
678 // Check if the trailing subview is nested and if yes, if one of its two-axis thumbs was hit.
679                                 int tdivdr = NSNotFound;
680                                 float toffset = 0.0;
681                                 NSPoint twhere = where;
682                                 NSRect trect = NSZeroRect;
683                                 if ((trailing = [trailing coupledSplitView])) {
684                                         tdivdr = [trailing RB___dividerHitBy:twhere relativeToView:self thickness:divt];
685                                         if (tdivdr!=NSNotFound) {
686                                                 trect = [trailing RB___dividerRect:tdivdr relativeToView:self];
687                                                 toffset = OTHER(twhere)-OTHER(trect.origin);
688                                         }
689                                 }
690 // Now we loop handling mouse events until we get a mouse up event, while showing the drag cursor.
691                                 [[RBSplitView cursor:RBSVDragCursor] push];
692                                 [self RB___setDragging:YES];
693                                 while ((theEvent = [NSApp nextEventMatchingMask:NSLeftMouseDownMask|NSLeftMouseDraggedMask|NSLeftMouseUpMask untilDate:[NSDate distantFuture] inMode:NSEventTrackingRunLoopMode dequeue:YES])&&([theEvent type]!=NSLeftMouseUp)) {
694 // Set up a local autorelease pool for the loop to prevent buildup of temporary objects.
695                                         NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
696                                         NSDisableScreenUpdates();
697 // Track the mouse along the main coordinate. 
698                                         [self RB___trackMouseEvent:theEvent from:where withBase:NSZeroPoint inDivider:i];
699                                         if (ldivdr!=NSNotFound) {
700 // Track any two-axis thumbs for the leading nested RBSplitView.
701                                                 [leading RB___trackMouseEvent:theEvent from:[self convertPoint:lwhere toView:leading] withBase:NSZeroPoint inDivider:ldivdr];
702                                         }
703                                         if (tdivdr!=NSNotFound) {
704 // Track any two-axis thumbs for the trailing nested RBSplitView.
705                                                 [trailing RB___trackMouseEvent:theEvent from:[self convertPoint:twhere toView:trailing] withBase:NSZeroPoint inDivider:tdivdr];
706                                         }
707                                         if (mustAdjust||[leading mustAdjust]||[trailing mustAdjust]) {
708 // The mouse was dragged and the subviews changed, so we must redisplay, as
709 // several divider rectangles may have changed.
710                                                 RBSplitView* sv = [self splitView];
711                                                 [sv?sv:self adjustSubviews];
712                                                 [super display];
713                                                 divdr = &dividers[i];
714 // Adjust to the new cursor coordinates.
715                                                 DIM(where) = DIM(divdr->origin)+offset;
716                                                 if ((ldivdr!=NSNotFound)&&![leading isCollapsed]) {
717 // Adjust for the leading nested RBSplitView's thumbs while it's not collapsed.
718                                                         lrect = [leading RB___dividerRect:ldivdr relativeToView:self];
719                                                         OTHER(lwhere) = OTHER(lrect.origin)+loffset;
720                                                 }
721                                                 if ((tdivdr!=NSNotFound)&&![trailing isCollapsed]) {
722 // Adjust for the trailing nested RBSplitView's thumbs while it's not collapsed.
723                                                         trect = [trailing RB___dividerRect:tdivdr relativeToView:self];
724                                                         OTHER(twhere) = OTHER(trect.origin)+toffset;
725                                                 }
726                                         }
727                                         NSEnableScreenUpdates();
728                                         [pool release];
729                                 }
730                                 [self RB___setDragging:NO];
731 // Redisplay the previous cursor.
732                                 [NSCursor pop];
733                         }
734                 }
735         }
738 // This will be called before the view will be redisplayed, so we adjust subviews if necessary.
739 - (BOOL)needsDisplay {
740         if (mustAdjust&&!isAdjusting) {
741                 [self adjustSubviews];
742                 return YES;
743         }
744         return [super needsDisplay];
747 // We implement awakeFromNib to restore the state. This works if an autosaveName is set in the nib.
748 - (void)awakeFromNib {
749         if ([RBSplitSubview instancesRespondToSelector:@selector(awakeFromNib)]) {
750                 [super awakeFromNib];
751         }
752         if (![self splitView]) {
753                 [self restoreState:YES];
754         }
757 // We check if subviews must be adjusted before redisplaying programmatically.
758 - (void)display {
759         if (mustAdjust&&!isAdjusting) {
760                 [self adjustSubviews];
761         }
762         [super display];
765 // This method draws the divider rectangles and then the two-axis thumbs if there are any.
766 - (void)drawRect:(NSRect)rect {
767         [super drawRect:rect];
768         if (!dividers) {
769                 return;
770         }
771         NSArray* subviews = [self RB___subviews];
772         int subcount = [subviews count];
773 // Return if there are no dividers to draw.
774         if (subcount<2) {
775                 return;
776         }
777         --subcount;
778         int i;
779 // Cache the divider image.
780         NSImage* divdr = [self divider];
781         float divt = [self dividerThickness];
782 // Loop over the divider rectangles.
783         for (i=0;i<subcount;i++) {
784 // Check if we need to draw this particular divider.
785                 if ([self needsToDrawRect:dividers[i]]) {
786                         RBSplitView* leading = [subviews objectAtIndex:i];
787                         RBSplitView* trailing = [subviews objectAtIndex:i+1];
788                         BOOL lexp = divdr?![leading isCollapsed]:NO;
789                         BOOL texp = divdr?![trailing isCollapsed]:NO;
790 // We don't draw the divider image if either of the neighboring subviews is a non-collapsed
791 // nested split view.
792                         BOOL nodiv = (lexp&&[leading coupledSplitView])||(texp&&[trailing coupledSplitView]);
793                         [self drawDivider:nodiv?nil:divdr inRect:dividers[i] betweenView:leading andView:trailing];
794                         if (divdr) {
795 // Draw the corresponding two-axis thumbs if the leading view is a nested RBSplitView.
796                                 if ((leading = [leading coupledSplitView])&&lexp) {
797                                         [leading RB___drawDividersIn:self forDividerRect:dividers[i] thickness:divt];
798                                 }
799 // Draw the corresponding two-axis thumbs if the trailing view is a nested RBSplitView.
800                                 if ((trailing = [trailing coupledSplitView])&&texp) {
801                                         [trailing RB___drawDividersIn:self forDividerRect:dividers[i] thickness:divt];
802                                 }
803                         }
804                 }
805         }
808 // This method draws dividers. You should never call it directly but you can override it when
809 // subclassing, if you need custom dividers. It draws the divider image centered in the divider rectangle.
810 // If we're drawing a two-axis thumb leading and trailing will be nil, and the rectangle
811 // will be the thumb rectangle.
812 // If there are nested split views this will be called once to draw the main divider rect,
813 // and again for every thumb.
814 - (void)drawDivider:(NSImage*)anImage inRect:(NSRect)rect betweenView:(RBSplitSubview*)leading andView:(RBSplitSubview*)trailing {
815 // Fill the view with the background color (if there's any). Don't draw the background again for
816 // thumbs.
817         if (leading||trailing) {
818                 NSColor* bg = [self background];
819                 if (bg) {
820                         [bg set];
821                         NSRectFillUsingOperation(rect,NSCompositeSourceOver);
822                 }
823         }
824 // Center the image, if there is one.
825         NSRect imrect = NSZeroRect;
826         NSRect dorect = NSZeroRect;
827         if (anImage) {
828                 imrect.size = dorect.size = [anImage size];
829                 dorect.origin = NSMakePoint(floorf(rect.origin.x+(rect.size.width-dorect.size.width)/2),
830                                                                         floorf(rect.origin.y+(rect.size.height-dorect.size.height)/2));
831         }
832 // Ask the delegate for the final rect where the image should be drawn.
833         if ([delegate respondsToSelector:@selector(splitView:willDrawDividerInRect:betweenView:andView:withProposedRect:)]) {
834                 dorect = [delegate splitView:self willDrawDividerInRect:rect betweenView:leading andView:trailing withProposedRect:dorect];
835         }
836 // Draw the image if the delegate returned a non-empty rect.
837         if (!NSIsEmptyRect(dorect)) {
838                 [anImage drawInRect:dorect fromRect:imrect operation:NSCompositeSourceOver fraction:1.0];
839         }
842 // This method should be called only from within the splitView:wasResizedFrom:to: delegate method
843 // to keep some specific subview the same size.
844 - (void)adjustSubviewsExcepting:(RBSplitSubview*)excepting {
845         [self RB___adjustSubviewsExcepting:[excepting isCollapsed]?nil:excepting];
848 // This method adjusts subviews and divider rectangles.
849 - (void)adjustSubviews {
850         [self RB___adjustSubviewsExcepting:nil];
853 // This resets the appropriate cursors for each divider according to the orientation.
854 // No cursors are shown if there is no divider image.
855 - (void)resetCursorRects {
856         if (!dividers) {
857                 return;
858         }
859         id del = [delegate respondsToSelector:@selector(splitView:cursorRect:forDivider:)]?delegate:nil;
860         NSArray* subviews = [self RB___subviews];
861         int divcount = [subviews count]-1;
862         if ((divcount<1)||![self divider]) {
863                 [del splitView:self cursorRect:NSZeroRect forDivider:0];
864                 return;
865         }
866         int i;
867         NSCursor* cursor = [RBSplitView cursor:[self isVertical]?RBSVVerticalCursor:RBSVHorizontalCursor];
868         float divt = [self dividerThickness];
869         for (i=0;i<divcount;i++) {
870                 RBSplitView* sub = [[subviews objectAtIndex:i] coupledSplitView];
871 // If the leading subview is a nested RBSplitView, add the thumb rectangles first.
872                 if (sub) {
873                         [sub  RB___addCursorRectsTo:self forDividerRect:dividers[i] thickness:divt];
874                 }
875                 sub = [[subviews objectAtIndex:i+1] coupledSplitView];
876 // If the trailing subview is a nested RBSplitView, add the thumb rectangles first.
877                 if (sub) {
878                         [sub  RB___addCursorRectsTo:self forDividerRect:dividers[i] thickness:divt];
879                 }
880 // Now add thedivider rectangle.
881                 NSRect divrect = dividers[i];
882                 if (del) {
883                         divrect = [del splitView:self cursorRect:divrect forDivider:i];
884                 }
885                 if (!NSIsEmptyRect(divrect)) {
886                         [self addCursorRect:divrect cursor:cursor];
887                 }
888         }
891 // These two methods encode and decode RBSplitViews. One peculiarity is that we encode the divider image's
892 // bitmap representation as data; this makes the nib files larger, but the user can just paste any image
893 // into the RBSplitView inspector - or use the default divider image - without having to include it into the
894 // project, too.
895 - (void)encodeWithCoder:(NSCoder *)coder {
896         [super encodeWithCoder:coder];
897         if ([coder allowsKeyedCoding]) {
898         [coder encodeConditionalObject:delegate forKey:@"delegate"];
899                 [coder encodeObject:autosaveName forKey:@"autosaveName"];
900                 [coder encodeObject:[divider TIFFRepresentation] forKey:@"divider"];
901                 [coder encodeObject:background forKey:@"background"];
902                 [coder encodeFloat:dividerThickness forKey:@"dividerThickness"];
903                 [coder encodeBool:isHorizontal forKey:@"isHorizontal"];
904                 [coder encodeBool:isCoupled forKey:@"isCoupled"];
905         } else {
906                 [coder encodeConditionalObject:delegate];
907                 [coder encodeObject:autosaveName];
908                 [coder encodeObject:[divider TIFFRepresentation]];
909                 [coder encodeObject:background];
910                 [coder encodeValueOfObjCType:@encode(typeof(dividerThickness)) at:&dividerThickness];
911                 [coder encodeValueOfObjCType:@encode(typeof(isHorizontal)) at:&isHorizontal];
912                 [coder encodeValueOfObjCType:@encode(typeof(isCoupled)) at:&isCoupled];
913         }
916 - (id)initWithCoder:(NSCoder *)coder {
917     if ((self = [super initWithCoder:coder])) {
918                 NSData* data = nil;
919                 float divt = 0.0;
920                 isCoupled = YES;
921                 isDragging = NO;
922                 isInScrollView = NO;
923                 canSaveState = NO;
924                 if ([coder allowsKeyedCoding]) {
925                         isCoupled = [coder decodeBoolForKey:@"isCoupled"];
926                         [self setDelegate:[coder decodeObjectForKey:@"delegate"]];
927                         [self setAutosaveName:[coder decodeObjectForKey:@"autosaveName"] recursively:NO];
928                         data = [coder decodeObjectForKey:@"divider"];
929                         [self setBackground:[coder decodeObjectForKey:@"background"]];
930                         divt = [coder decodeFloatForKey:@"dividerThickness"];
931                         isHorizontal = [coder decodeBoolForKey:@"isHorizontal"];
932                 } else {
933                         [self setDelegate:[coder decodeObject]];
934                         [self setAutosaveName:[coder decodeObject] recursively:NO];
935                         data = [coder decodeObject];
936                         [self setBackground:[coder decodeObject]];
937                         [coder decodeValueOfObjCType:@encode(typeof(divt)) at:&divt];
938                         [coder decodeValueOfObjCType:@encode(typeof(isHorizontal)) at:&isHorizontal];
939                         [coder decodeValueOfObjCType:@encode(typeof(isCoupled)) at:&isCoupled];
940                 }
941                 dividers = NULL;
942                 if (data) {
943                         NSBitmapImageRep* rep = [NSBitmapImageRep imageRepWithData:data];
944                         NSImage* image = [[[NSImage alloc] initWithSize:[rep size]] autorelease];
945                         [image setFlipped:YES];
946                         [image addRepresentation:rep];
947                         [self setDivider:image];
948                 } else {
949                         [self setDivider:nil];
950                 }
951                 [self setDividerThickness:divt];
952                 [self setMustAdjust];
953                 [self performSelector:@selector(viewDidMoveToSuperview) withObject:nil afterDelay:0.0];
954                 [self performSelector:@selector(RB___adjustOutermostIfNeeded) withObject:nil afterDelay:0.0];
955         }
956     return self;
959 @end
961 @implementation RBSplitView (RB___ViewAdditions)
963 // This sets the dragging status flag. After clearing the flag, the state must be saved explicitly.
964 - (void)RB___setDragging:(BOOL)flag {
965         BOOL save = isDragging&&!flag;
966         isDragging = flag;
967         if (save) {
968                 [self saveState:NO];
969         }
972 // This returns the number of visible subviews.
973 - (unsigned int)RB___numberOfSubviews {
974         unsigned int result = 0;
975         NSEnumerator* enumerator = [[self subviews] objectEnumerator];
976         RBSplitSubview* sub;
977         while ((sub = [enumerator nextObject])) {
978                 ++result;
979         }
980         return result;
983 // This returns the origin coordinate of the Nth divider.
984 - (float)RB___dividerOrigin:(int)indx {
985         float result = 0.0;
986         if (dividers) {
987                 BOOL ishor = [self isHorizontal];
988                 result = DIM(dividers[indx].origin);
989         }
990         return result;
993 // This returns an array with all non-hidden subviews.
994 - (NSArray*)RB___subviews {
995         NSMutableArray* result = [NSMutableArray arrayWithArray:[self subviews]];
996         int i;
997         for (i=[result count]-1;i>=0;i--) {
998                 RBSplitSubview* view = [result objectAtIndex:i];
999                 if ([view isHidden]) {
1000                         [result removeObjectAtIndex:i];
1001                 }
1002         }
1003         return result;
1006 // This returns the actual value set in dividerThickness. 
1007 - (float)RB___dividerThickness {
1008         return dividerThickness;
1011 // This method returns the actual dimension occupied by the subviews; that is, without dividers.
1012 - (float)RB___dimensionWithoutDividers {
1013         BOOL ishor = [self isHorizontal];
1014         NSSize size = [self frame].size;
1015         return fMAX(1.0,DIM(size)-[self dividerThickness]*([self RB___numberOfSubviews]-1));
1018 // This method returns one of the divider rectangles, or NSZeroRect if the index is invalid.
1019 // If view is non-nil, the rect will be expressed in that view's coordinates. We assume
1020 // that view is a superview of self.
1021 - (NSRect)RB___dividerRect:(unsigned)indx relativeToView:(RBSplitView*)view {
1022         if (dividers&&(indx<[self RB___numberOfSubviews]-1)) {
1023                 NSRect result = dividers[indx];
1024                 if (view&&(view!=self)) {
1025                         result = [self convertRect:result toView:view];
1026                 }
1027                 return result;
1028         }
1029         return NSZeroRect;
1032 // Returns the index of the divider hit by the point, or NSNotFound if none.
1033 // point is in coordinates relative to view. delta is the divider thickness added
1034 // to both ends of the divider rect, to accomodate two-axis thumbs.
1035 - (unsigned)RB___dividerHitBy:(NSPoint)point relativeToView:(RBSplitView*)view thickness:(float)delta {
1036         if (!dividers) {
1037                 return NSNotFound;
1038         }
1039         int divcount = [self RB___numberOfSubviews]-1;
1040         if (divcount<1) {
1041                 return NSNotFound;
1042         }
1043         int i;
1044         BOOL ishor = [self isHorizontal];
1045         point = [self convertPoint:point fromView:view];
1046         for (i=0;i<divcount;i++) {
1047                 NSRect divdr = dividers[i];
1048                 OTHER(divdr.origin) -= delta;
1049                 OTHER(divdr.size) += 2*delta;
1050                 if ([self mouse:point inRect:divdr]) {
1051                         return i;
1052                 }
1053         }
1054         return NSNotFound;
1057 // This method sets a flag to clear all fractions before adjusting.
1058 - (void)RB___setMustClearFractions {
1059         mustClearFractions = YES;
1062 // This local method asks the delegate if we should resize the trailing subview or the window
1063 // when a divider is dragged. Not called if we're inside an NSScrollView.
1064 - (BOOL)RB___shouldResizeWindowForDivider:(unsigned int)indx betweenView:(RBSplitSubview*)leading andView:(RBSplitSubview*)trailing willGrow:(BOOL)grow {
1065         if (!isInScrollView&&[delegate respondsToSelector:@selector(splitView:shouldResizeWindowForDivider:betweenView:andView:willGrow:)]) {
1066                 return [delegate splitView:self shouldResizeWindowForDivider:indx betweenView:leading andView:trailing willGrow:grow];
1067         }
1068         return NO;
1071 // This local method tries to expand the leading subview (which is assumed to be collapsed). Delta should be positive.
1072 - (void)RB___tryToExpandLeading:(RBSplitSubview*)leading divider:(unsigned int)indx trailing:(RBSplitSubview*)trailing delta:(float)delta {
1073         NSWindow* window = nil;
1074         NSView* document = nil;
1075         NSSize maxsize = NSMakeSize(WAYOUT,WAYOUT);
1076         NSRect frame = NSZeroRect;
1077         NSRect screen = NSMakeRect(0,0,WAYOUT,WAYOUT);
1078         BOOL ishor = NO;
1079 // First we ask the delegate, if there's any, if the window should resize.
1080         BOOL dowin = ([self RB___shouldResizeWindowForDivider:indx betweenView:leading andView:trailing willGrow:YES]);
1081         if (dowin) {
1082 // We initialize the other local variables only if we need them for the window.
1083                 ishor = [self isHorizontal];
1084                 document = [[self enclosingScrollView] documentView];
1085                 if (document) {
1086                         frame = [document frame];
1087                 } else {
1088                         window = [self window];
1089                         frame = [window frame];
1090                         maxsize = [window maxSize];
1091                         screen = [[NSScreen mainScreen] visibleFrame];
1092                 }
1093         }
1094 // The mouse has to move over half of the expanded size (plus hysteresis) and the expansion shouldn't
1095 // reduce the trailing subview to less than its minimum size (or grow the window beyond its maximum).
1096         float limit = [leading minDimension];
1097         float dimension = 0.0;
1098         if (dowin) {
1099                 float maxd = fMAX(0.0,(ishor?frame.origin.y-screen.origin.y:(screen.origin.x+screen.size.width)-(frame.origin.x+frame.size.width)));
1100                 dimension = fMIN(DIM(maxsize)-DIM(frame.size),maxd);
1101         } else {
1102                 dimension = trailing?[trailing dimension]:WAYOUT;
1103         }
1104         if (limit>dimension) {
1105                 return;
1106         }
1107         if (!dowin&&trailing) {
1108                 limit += [trailing minDimension];
1109                 if (limit>dimension) {
1110 // If the trailing subview is going below its minimum, we try to collapse it first.
1111 // However, we don't collapse if that would cause the leading subview to become larger than its maximum.
1112                         if (([trailing canCollapse])&&(delta>(0.5+HYSTERESIS)*dimension)&&([leading maxDimension]<=dimension)) {
1113                                 delta = -[trailing RB___collapse];
1114                                 [leading changeDimensionBy:delta mayCollapse:NO move:NO];
1115                         }
1116                         return;
1117                 }
1118         }
1119 // The leading subview may be expanded normally.
1120         delta = -[leading changeDimensionBy:delta mayCollapse:NO move:NO];
1121         if (dowin) {
1122 // If it does expand, we widen the window.
1123                 DIM(frame.size) -= delta;
1124                 if (ishor) {
1125                         DIM(frame.origin) += delta;
1126                 }
1127                 if (document) {
1128                         [document setFrame:frame];
1129                         [document setNeedsDisplay:YES];
1130                 } else {
1131                         [window setFrame:frame display:YES];
1132                 }
1133                 [self setMustAdjust];
1134         } else {
1135 // If it does expand, we shorten the trailing subview.
1136                 [trailing changeDimensionBy:delta mayCollapse:NO move:YES];
1137         }
1140 // This local method tries to shorten the leading subview. Both subviews are assumed to be expanded.
1141 // delta should be negative. If always is NO, the subview will be shortened only if it might also be
1142 // collapsed; otherwise, it's shortened as much as possible.
1143 - (void)RB___tryToShortenLeading:(RBSplitSubview*)leading divider:(unsigned int)indx trailing:(RBSplitSubview*)trailing delta:(float)delta always:(BOOL)always {
1144         NSWindow* window = nil;
1145         NSView* document = nil;
1146         NSSize minsize = NSZeroSize;
1147         NSRect frame = NSZeroRect;
1148         BOOL ishor = NO;
1149 // First we ask the delegate, if there's any, if the window should resize.
1150         BOOL dowin = ([self RB___shouldResizeWindowForDivider:indx betweenView:leading andView:trailing willGrow:NO]);
1151         if (dowin) {
1152 // We initialize the other local variables only if we need them for the window.
1153                 ishor = [self isHorizontal];
1154                 document = [[self enclosingScrollView] documentView];
1155                 if (document) {
1156                         frame = [document frame];
1157                 } else {
1158                         window = [self window];
1159                         frame = [window frame];
1160                         minsize = [window minSize];
1161                 }
1162         }
1163 // We avoid making the trailing subview larger than its maximum, or the window smaller than its minimum.
1164         float limit = 0.0;
1165         if (dowin) {
1166                 limit = DIM(frame.size)-DIM(minsize);
1167         } else {
1168                 limit = trailing?([trailing maxDimension]-[trailing dimension]):WAYOUT;
1169         }
1170         if (-delta>limit) {
1171                 if (always) {
1172                         delta = -limit;
1173                 } else {
1174                         return;
1175                 }
1176         }
1177         BOOL okl = limit>=[leading dimension];
1178         if (always||okl) {
1179 // Resize leading.
1180                 delta = -[leading changeDimensionBy:delta mayCollapse:okl move:NO];
1181                 if (dowin) {
1182 // Resize the window.
1183                         DIM(frame.size) -= delta;
1184                         if (ishor) {
1185                                 DIM(frame.origin) += delta;
1186                         }
1187                         if (document) {
1188                                 [document setFrame:frame];
1189                                 [document setNeedsDisplay:YES];
1190                         } else {
1191                                 [window setFrame:frame display:YES];
1192                         }
1193                         [self setMustAdjust];
1194                 } else {
1195 // Otherwise, resize trailing.
1196                         [trailing changeDimensionBy:delta mayCollapse:NO move:YES];
1197                 }
1198         }
1201 // This local method tries to shorten the trailing subview. Both subviews are assumed to be expanded.
1202 // delta should be positive. If always is NO, the subview will be shortened only if it might also be
1203 // collapsed; otherwise, it's shortened as much as possible.
1204 - (void)RB___tryToShortenTrailing:(RBSplitSubview*)trailing divider:(unsigned int)indx leading:(RBSplitSubview*)leading delta:(float)delta always:(BOOL)always {
1205         NSWindow* window = nil;
1206         NSView* document = nil;
1207         NSSize maxsize = NSMakeSize(WAYOUT,WAYOUT);
1208         NSRect frame = NSZeroRect;
1209         NSRect screen = NSMakeRect(0,0,WAYOUT,WAYOUT);
1210         BOOL ishor = NO;
1211 // First we ask the delegate, if there's any, if the window should resize.
1212         BOOL dowin = ([self RB___shouldResizeWindowForDivider:indx betweenView:leading andView:trailing willGrow:YES]);
1213         if (dowin) {
1214 // We initialize the other local variables only if we need them for the window.
1215                 ishor = [self isHorizontal];
1216                 document = [[self enclosingScrollView] documentView];
1217                 if (document) {
1218                         frame = [document frame];
1219                 } else {
1220                         window = [self window];
1221                         frame = [window frame];
1222                         maxsize = [window maxSize];
1223                         screen = [[NSScreen mainScreen] visibleFrame];
1224                 }
1225         }
1226 // We avoid making the leading subview larger than its maximum, or the window larger than its maximum.
1227         float limit = 0.0;
1228         if (dowin) {
1229                 float maxd = fMAX(0.0,(ishor?frame.origin.y-screen.origin.y:(screen.origin.x+screen.size.width)-(frame.origin.x+frame.size.width)));
1230                 limit = fMIN(DIM(maxsize)-DIM(frame.size),maxd);
1231         } else {
1232                 limit = [leading maxDimension]-[leading dimension];
1233         }
1234         if (delta>limit) {
1235                 if (always) {
1236                         delta = limit;
1237                 } else {
1238                         return;
1239                 }
1240         }
1241         BOOL okl = dowin||(limit>=(trailing?[trailing dimension]:WAYOUT));
1242         if (always||okl) {
1243                 if (dowin) {
1244 // If we should resize the window, resize leading, then the window.
1245                         delta = [leading changeDimensionBy:delta mayCollapse:NO move:NO];
1246                         DIM(frame.size) += delta;
1247                         if (ishor) {
1248                                 DIM(frame.origin) -= delta;
1249                         }
1250                         if (document) {
1251                                 [document setFrame:frame];
1252                                 [document setNeedsDisplay:YES];
1253                         } else {
1254                                 [window setFrame:frame display:YES];
1255                         }
1256                         [self setMustAdjust];
1257                 } else {
1258 // Otherwise, resize trailing, then leading.
1259                         if (trailing) {
1260                                 delta = -[trailing changeDimensionBy:-delta mayCollapse:okl move:YES];
1261                         }
1262                         [leading changeDimensionBy:delta mayCollapse:NO move:NO];
1263                 }
1264         }
1267 // This method tries to expand the trailing subview (which is assumed to be collapsed).
1268 - (void)RB___tryToExpandTrailing:(RBSplitSubview*)trailing leading:(RBSplitSubview*)leading delta:(float)delta {
1269 // The mouse has to move over half of the expanded size (plus hysteresis) and the expansion shouldn't
1270 // reduce the leading subview to less than its minimum size. If it does, we try to collapse it first.
1271 // However, we don't collapse if that would cause the trailing subview to become larger than its maximum.
1272         float limit = trailing?[trailing minDimension]:0.0;
1273         float dimension = [leading dimension];
1274         if (limit>dimension) {
1275                 return;
1276         }
1277         limit += [leading minDimension];
1278         if (limit>dimension) {
1279                 if ([leading canCollapse]&&(-delta>(0.5+HYSTERESIS)*dimension)&&((trailing?[trailing maxDimension]:0.0)<=dimension)) {
1280                         delta = -[leading RB___collapse];
1281                         [trailing changeDimensionBy:delta mayCollapse:NO move:YES];
1282                 }
1283                 return;
1284         }
1285 // The trailing subview may be expanded normally. If it does expand, we shorten the leading subview.
1286         if (trailing) {
1287                 delta = -[trailing changeDimensionBy:-delta mayCollapse:NO move:YES];
1288         }
1289         [leading changeDimensionBy:delta mayCollapse:NO move:NO];
1293 // This method is called by the mouseDown:method for every tracking event. It's separated out as it's
1294 // called from the Interface Builder palette in a slightly different way, and also if you have a
1295 // separate drag view designated by the delegate. You'll never need to call this directly.
1296 // theEvent is the event (which should be a NSLeftMouseDragged event).
1297 // where is the point where the original mouse-down happened, corrected for the current divider position,
1298 // and expressed in local coordinates.
1299 // base is an offset (x,y) applied to the mouse location (usually will be zero)
1300 // indx is the number of the divider that's being dragged.
1301 - (void)RB___trackMouseEvent:(NSEvent*)theEvent from:(NSPoint)where withBase:(NSPoint)base inDivider:(unsigned)indx {
1302         NSPoint result;
1303         NSArray* subviews = [self RB___subviews];
1304         int subcount = [subviews count];
1305         int k;
1306 // leading and trailing point at the subviews immediately leading and trailing the divider being tracked
1307         RBSplitSubview* leading = [subviews objectAtIndex:indx];
1308         RBSplitSubview* trailing = [subviews objectAtIndex:indx+1];
1309 // convert the mouse coordinates to apply to the same system the divider rects are in.
1310         NSPoint mouse = [self convertPoint:[theEvent locationInWindow] fromView:nil];
1311         mouse.x -= base.x;
1312         mouse.y -= base.y;
1313         result.x = mouse.x-where.x;
1314         result.y = mouse.y-where.y;
1315 // delta is the actual amount the mouse has moved in the relevant coordinate since the last event.
1316         BOOL ishor = [self isHorizontal];
1317         float delta = DIM(result);
1318         if (delta<0.0) {
1319 // Negative delta means the mouse is being moved left or upwards.
1320 // firstLeading will point at the first expanded subview to the left (or upwards) of the divider.
1321 // If there's none (all subviews are collapsed) it will point at the nearest subview.
1322                 RBSplitSubview* firstLeading = leading;
1323                 k = indx;
1324                 while (![firstLeading canShrink]) {
1325                         if (--k<0) {
1326                                 firstLeading = leading;
1327                                 break;
1328                         }
1329                         firstLeading = [subviews objectAtIndex:k];
1330                 }
1331                 if (isInScrollView) {
1332                         trailing = nil;
1333                 }
1334 // If the trailing subview is collapsed, it might be expanded if some conditions are met.
1335                 if ([trailing isCollapsed]) {
1336                         [self RB___tryToExpandTrailing:trailing leading:firstLeading delta:delta];
1337                 } else {
1338                         [self RB___tryToShortenLeading:firstLeading divider:indx trailing:trailing delta:delta always:YES];
1339                 }
1340         } else if (delta>0.0) {
1341 // Positive delta means the mouse is being moved right or downwards.
1342 // firstTrailing will point at the first expanded subview to the right (or downwards) of the divider.
1343 // If there's none (all subviews are collapsed) it will point at the nearest subview.
1344                 RBSplitSubview* firstTrailing = nil;
1345                 if (!isInScrollView) {
1346                         firstTrailing = trailing;
1347                         k = indx+1;
1348                         while (![firstTrailing canShrink]) {
1349                                 if (++k>=subcount) {
1350                                         firstTrailing = trailing;
1351                                         break;
1352                                 }
1353                                 firstTrailing = [subviews objectAtIndex:k];
1354                         }
1355                 }
1356 // If the leading subview is collapsed, it might be expanded if some conditions are met.
1357                 if ([leading isCollapsed]) {
1358                         [self RB___tryToExpandLeading:leading divider:indx trailing:firstTrailing delta:delta];
1359                 } else {
1360 // The leading subview is not collapsed, so we try to shorten or even collapse it
1361                         [self RB___tryToShortenTrailing:firstTrailing divider:indx leading:leading delta:delta always:YES];
1362                 }
1363         }
1366 // This is called for nested RBSplitViews, to add the cursor rects for the two-axis thumbs.
1367 - (void)RB___addCursorRectsTo:(RBSplitView*)masterView forDividerRect:(NSRect)rect thickness:(float)delta {
1368         if (dividers&&[self divider]) {
1369                 NSArray* subviews = [self RB___subviews];
1370                 int divcount = [subviews count]-1;
1371                 if (divcount<1) {
1372                         return;
1373                 }
1374                 int i;
1375                 NSCursor* cursor = [RBSplitView cursor:RBSV2WayCursor];
1376                 BOOL ishor = [self isHorizontal];
1377 // Loop over the divider rectangles, intersect them with the view's own, and add the thumb rectangle
1378 // to the containing split view.
1379                 for (i=0;i<divcount;i++) {
1380                         NSRect divdr = dividers[i];
1381                         divdr.origin = [self convertPoint:divdr.origin toView:masterView];
1382                         OTHER(divdr.origin) -= delta;
1383                         OTHER(divdr.size) += 2*delta;
1384                         divdr = NSIntersectionRect(divdr,rect);
1385                         if (!NSIsEmptyRect(divdr)) {
1386                                 [masterView addCursorRect:divdr cursor:cursor];
1387                         }
1388                 }
1389         }
1392 // This is called for nested RBSplitViews, to draw the two-axis thumbs.
1393 - (void)RB___drawDividersIn:(RBSplitView*)masterView forDividerRect:(NSRect)rect thickness:(float)delta {
1394         if (!dividers) {
1395                 return;
1396         }
1397         NSArray* subviews = [self RB___subviews];
1398         int divcount = [subviews count]-1;
1399         if (divcount<1) {
1400                 return;
1401         }
1402         int i;
1403         BOOL ishor = [self isHorizontal];
1404 // Get the outer split view's divider image.
1405         NSImage* image = [masterView divider];
1406 // Loop over the divider rectangles, intersect them with the view's own, and draw the thumb there.
1407         for (i=0;i<divcount;i++) {
1408                 NSRect divdr = dividers[i];
1409                 divdr.origin = [self convertPoint:divdr.origin toView:masterView];
1410                 OTHER(divdr.origin) -= delta;
1411                 OTHER(divdr.size) += 2*delta;
1412                 divdr = NSIntersectionRect(divdr,rect);
1413                 if (!NSIsEmptyRect(divdr)) {
1414                         [masterView drawDivider:image inRect:divdr betweenView:nil andView:nil];
1415                 }
1416         }
1419 // This is usually called from initWithCoder to ensure that the outermost RBSplitView is
1420 // properly adjusted when first displayed.
1421 - (void)RB___adjustOutermostIfNeeded {
1422         RBSplitView* sv = [self splitView];
1423         if (sv) {
1424                 [sv RB___adjustOutermostIfNeeded];
1425                 return;
1426         }
1427         if (mustAdjust&&!isAdjusting) {
1428                 [self adjustSubviews];
1429         }
1432 // Here we try to keep all subviews adjusted in as natural a manner as possible, given the constraints.
1433 // The main idea is to always keep the RBSplitView completely covered by dividers and subviews, have at
1434 // least one expanded subview, and never make a subview smaller than its minimum dimension, or larger
1435 // than its maximum dimension.
1436 // We try to account for most unusual situations but this may fail under some circumstances. YMMV.
1437 - (void)RB___adjustSubviewsExcepting:(RBSplitSubview*)excepting {
1438         mustAdjust = NO;
1439         NSArray* subviews = [self RB___subviews];
1440         unsigned subcount = [subviews count];
1441         if (subcount<1) {
1442                 return;
1443         }
1444         NSRect bounds = [self bounds];
1445 // Never adjust if the splitview itself is collapsed.
1446         if ((bounds.size.width<1.0)||(bounds.size.height<1.0)) {
1447                 return;
1448         }
1449 // Prevents adjustSubviews being called recursively, which unfortunately may happen otherwise.
1450         if (isAdjusting) {
1451                 return;
1452         }
1453         isAdjusting = YES;
1454 // Tell the delegate we're about to adjust subviews.
1455         if ([delegate respondsToSelector:@selector(willAdjustSubviews:)]) {
1456                 [delegate willAdjustSubviews:self];
1457                 bounds = [self bounds];
1458         }
1459         unsigned divcount = subcount-1;
1460         if (divcount<1) {
1461 // No dividers at all.
1462                 if (dividers) {
1463                         free(dividers);
1464                         dividers = NULL;
1465                 }
1466         } else {
1467 // Try to allocate or resize if we already have a dividers array.
1468                 unsigned long divsiz = sizeof(NSRect)*divcount;
1469                 dividers = dividers?reallocf(dividers,divsiz):malloc(divsiz);
1470                 if (!dividers) {
1471                         return;
1472                 }
1473         }
1474 // This C array of subviewCaches is used to cache the subview information.
1475         subviewCache* caches = malloc(sizeof(subviewCache)*subcount);
1476         double realsize = 0.0;
1477         double expsize = 0.0;
1478         float newsize = 0.0;
1479         float effsize = 0.0;
1480         float limit;
1481         subviewCache* curr;
1482         unsigned int i;
1483         BOOL ishor = [self isHorizontal];
1484         float divt = [self dividerThickness];
1485 // First we loop over subviews and cache their information.
1486         for (i=0;i<subcount;i++) {
1487                 curr = &caches[i];
1488                 [[subviews objectAtIndex:i] RB___copyIntoCache:curr];
1489         }
1490 // This is a counter to limit the outer loop to three iterations (six if excepting is non-nil).
1491         int sanity = excepting?-3:0;
1492         while (sanity++<3) {
1493 // We try to accomodate the exception for the first group of loops, turn it off for the second.
1494                 if (sanity==1) {
1495                         excepting = nil;
1496                 }
1497 // newsize is the available space for actual subviews (so dividers don't count). It will be an integer.
1498 // Same as calling [self RB___dimensionWithoutDividers].
1499                 unsigned smallest = 0;
1500                 float smalldim = -1.0;
1501                 BOOL haveexp = NO;
1502 // Loop over subviews and sum the expanded dimensions into expsize, including fractions.
1503 // Also find the collapsed subview with the smallest minimum dimension.
1504                 for (i=0;i<subcount;i++) {
1505                         curr = &caches[i];
1506                         curr->constrain = NO;
1507                         if (curr->size>0.0) {
1508                                 expsize += curr->size;
1509                                 if (!isInScrollView) {
1510 // ignore fractions if we're in a NSScrollView, however.
1511                                         expsize += curr->fraction;
1512                                 }
1513                                 haveexp = YES;
1514                         } else {
1515                                 limit = [curr->sub minDimension];
1516                                 if (smalldim>limit) {
1517                                         smalldim = limit;
1518                                         smallest = i;
1519                                 }
1520                         }
1521                 }
1522 // haveexp should be YES at this point. If not, all subviews were collapsed; can't have that, so we 
1523 // expand the smallest subview (or the first, if all have the same minimum).
1524                 curr = &caches[smallest];
1525                 if (!haveexp) {
1526                         curr->size = [curr->sub minDimension];
1527                         curr->fraction = 0.0;
1528                         expsize += curr->size;
1529                 }
1530                 if (isInScrollView) {
1531 // If we're inside an NSScrollView, we just grow the view to accommodate the subviews, instead of 
1532 // the other way around.
1533                         DIM(bounds.size) = expsize;
1534                         break;
1535                 } else {
1536 // If the total dimension of all expanded subviews is less than 1.0 we set the dimension of the smallest
1537 // subview (which we're sure is expanded at this point) to the available space.
1538                         newsize = DIM(bounds.size)-divcount*divt;
1539                         if (expsize<1.0) {
1540                                 curr->size = newsize;
1541                                 curr->fraction = 0.0;
1542                                 expsize = newsize;
1543                         }
1544 // Loop over the subviews and check if they're within the limits after scaling. We also recalculate the
1545 // exposed size and repeat until no more subviews hit the constraints during that loop.
1546                         BOOL constrained;
1547                         effsize = newsize;// we're caching newsize here, this is an integer.
1548                         do {
1549 // scale is the scalefactor by which all views should be scaled - assuming none have constraints.
1550 // It's a double to (hopefully) keep rounding errors small enough for all practical purposes.
1551                                 double scale = newsize/expsize;
1552                                 constrained = NO;
1553                                 realsize = 0.0;
1554                                 expsize = 0.0;
1555                                 for (i=0;i<subcount;i++) {
1556 // Loop over the cached subview info.
1557                                         curr = &caches[i];
1558                                         if (curr->size>0.0) {
1559 // Check non-collapsed subviews only.
1560                                                 if (!curr->constrain) {
1561 // Check non-constrained subviews only; calculate the proposed new size.
1562                                                         float cursize = (curr->size+curr->fraction)*scale;
1563 // Check if we hit a limit. limit will contain either the max or min dimension, whichever was hit.
1564                                                         if (([curr->sub RB___animationData:NO resize:NO]&&((limit = curr->size)>=0.0))||
1565                                                                 ((curr->sub==excepting)&&((limit = [curr->sub dimension])>0.0))||
1566                                                                 (cursize<(limit = [curr->sub minDimension]))||
1567                                                                 (cursize>(limit = [curr->sub maxDimension]))) {
1568 // If we hit a limit, we mark the view and set to repeat the loop; non-constrained subviews will
1569 // have to be recalculated.
1570                                                                 curr->constrain = constrained = YES;
1571 // We set the new size to the limit we hit, and subtract it from the total size to be subdivided.
1572                                                                 cursize = limit;
1573                                                                 curr->fraction = 0.0;
1574                                                                 newsize -= cursize;
1575                                                         } else {
1576 // If we didn't hit a limit, we round the size to the nearest integer and recalculate the fraction. 
1577                                                                 double rem = fmod(cursize,1.0);
1578                                                                 cursize -= rem;
1579                                                                 if (rem>0.5) {
1580                                                                         ++cursize;
1581                                                                         --rem;
1582                                                                 }
1583                                                                 expsize += cursize;
1584                                                                 curr->fraction = rem;
1585                                                         }
1586 // We store the new size in the cache.
1587                                                         curr->size = cursize;
1588                                                 }
1589 // And add the full size with fraction to the actual sum of all expanded subviews.
1590                                                 realsize += curr->size+curr->fraction;
1591                                         }
1592                                 }
1593 // At this point, newsize will be the sum of the new dimensions of non-constrained views.
1594 // expsize will be the sum of the recalculated dimensions of the same views, if any.
1595 // We repeat the loop if any view has been recently constrained, and if there are any
1596 // unconstrained views left.
1597                         } while (constrained&&(expsize>0.0));
1598 // At this point, the difference between realsize and effsize should be less than 1 pixel.
1599 // realsize is the total size of expanded subviews as recalculated above, and
1600 // effsize is the value realsize should have.
1601                         limit = realsize-effsize;
1602                         if (limit>=1.0) {
1603 // If realsize is larger than effsize by 1 pixel or more, we will need to collapse subviews to make room.
1604 // This in turn might expand previously collapsed subviews. So, we'll try collapsing constrained subviews
1605 // until we're back into range, and then recalculate everything from the beginning.
1606                                 for (i=0;i<subcount;i++) {
1607                                         curr = &caches[i];
1608                                         if (curr->constrain&&(curr->sub!=excepting)&&([curr->sub RB___animationData:NO resize:NO]==nil)&&[curr->sub canCollapse]) {
1609                                                 realsize -= curr->size;
1610                                                 if (realsize<1.0) {
1611                                                         break;
1612                                                 }
1613                                                 curr->size = 0.0;
1614                                                 if ((realsize-effsize)<1.0) {
1615                                                         break;
1616                                                 }
1617                                         }
1618                                 }
1619                         } else if (limit<=-1.0) {
1620 // If realsize is smaller than effsize by 1 pixel or more, we will need to expand subviews.
1621 // This in turn might collapse previously expanded subviews. So, we'll try expanding collapsed subviews
1622 // until we're back into range, and then recalculate everything from the beginning.
1623                                 for (i=0;i<subcount;i++) {
1624                                         curr = &caches[i];
1625                                         if (curr->size<=0.0) {
1626                                                 curr->size = [curr->sub minDimension];
1627                                                 curr->fraction = 0.0;
1628                                                 realsize += curr->size;
1629                                                 if ((realsize-effsize)>-1.0) {
1630                                                         break;
1631                                                 }
1632                                         }
1633                                 }
1634                         } else {
1635 // The difference is less than 1 pixel, meaning that in all probability our calculations are
1636 // exact or off by at most one pixel after rounding, so we break the loop here.
1637                                 break;
1638                         }
1639                 }
1640 // After passing through the outer loop a few times, the frames may still be wrong, but there's nothing
1641 // else we can do about it. You probably should avoid this by some other means like setting a minimum
1642 // or maximum size for the window, for instance, or leaving at least one unlimited subview.
1643         }
1644 // newframe is used to reset all subview frames. Subviews always fill the entire RBSplitView along the
1645 // current orientation.
1646         NSRect newframe = NSMakeRect(0.0,0.0,bounds.size.width,bounds.size.height);
1647 // We now loop over the subviews yet again and set the definite frames, also recalculating the
1648 // divider rectangles as we go along, and collapsing and expanding subviews whenever requested.
1649         RBSplitSubview* last = nil;
1650 // And we make a note if there's any nested RBSplitView.
1651         int nested = NSNotFound;
1652         newsize = DIM(bounds.size)-divcount*divt;
1653         for (i=0;i<subcount;i++) {
1654                 curr = &caches[i];
1655 // If we have a nested split view store its index.
1656                 if ((nested==NSNotFound)&&([curr->sub asSplitView]!=nil)) {
1657                         nested = i;
1658                 }
1659 // Adjust the subview to the correct origin and resize it to fit into the "other" dimension.
1660                 curr->rect.origin = newframe.origin;
1661                 OTHER(curr->rect.size) = OTHER(newframe.size);
1662                 DIM(curr->rect.size) = curr->size;
1663 // Clear fractions for expanded subviews if requested.
1664                 if ((curr->size>0.0)&&mustClearFractions) {
1665                         curr->fraction = 0.0;
1666                 }
1667 // Ask the subview to do the actual moving/resizing etc. from the cache.
1668                 [curr->sub RB___updateFromCache:curr withTotalDimension:effsize];
1669 // Step to the next position and record the subview if it's not collapsed.
1670                 DIM(newframe.origin) += curr->size;
1671                 if (curr->size>0.0) {
1672                         last = curr->sub;
1673                 }
1674                 if (i==divcount) {
1675 // We're at the last subview, so we now check if the actual and calculated dimensions
1676 // are the same.
1677                         float remain = DIM(bounds.size)-DIM(newframe.origin);
1678                         if (last&&(fabsf(remain)>0.0)) {
1679 // We'll resize the last expanded subview to whatever it takes to squeeze within the frame.
1680 // Normally the change should be at most one pixel, but if too many subviews were constrained,
1681 // this may be a large value, and the last subview may be resized beyond its constraints;
1682 // there's nothing else to do at this point.
1683                                 newframe = [last frame];
1684                                 DIM(newframe.size) += remain;
1685                                 [last RB___setFrameSize:newframe.size withFraction:[last RB___fraction]-remain];
1686 // And we loop back over the rightmost dividers (if any) to adjust their offsets.
1687                                 while ((i>0)&&(last!=[subviews objectAtIndex:i])) {
1688                                         DIM(dividers[--i].origin) += remain;
1689                                 }
1690                                 break;
1691                         }
1692                 } else {
1693 // For any but the last subview, we just calculate the divider frame.
1694                         DIM(newframe.size) = divt;
1695                         dividers[i] = newframe;
1696                         DIM(newframe.origin) += divt;
1697                 }
1698         }
1699 // We resize our frame at this point, if we're inside an NSScrollView.
1700         if (isInScrollView) {
1701                 [super setFrameSize:bounds.size];
1702         }
1703 // If there was at least one nested RBSplitView, we loop over the subviews and adjust those that need it.
1704         for (i=nested;i<subcount;i++) {
1705                 curr = &caches[i];
1706                 RBSplitView* sv = [curr->sub asSplitView];
1707                 if ([sv mustAdjust]) {
1708                         [sv adjustSubviews];
1709                 }
1710         }
1711 // Free the cache array.
1712         free(caches);
1713 // Clear cursor rects.
1714         mustAdjust = NO;
1715         mustClearFractions = NO;
1716         [[self window] invalidateCursorRectsForView:self];
1717 // Save the state for all subviews.
1718         if (!isDragging) {
1719                 [self saveState:NO];
1720         }
1721 // If we're a nested RBSplitView, also invalidate cursorRects for the superview.
1722         RBSplitView* sv = [self couplingSplitView];
1723         if (sv) {
1724                 [[self window] invalidateCursorRectsForView:sv];
1725         }
1726         isAdjusting = NO;
1727 // Tell the delegate we're finished.
1728         if ([delegate respondsToSelector:@selector(didAdjustSubviews:)]) {
1729                 [delegate didAdjustSubviews:self];
1730         }
1733 @end