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