Change name of window menu items
[MacVim.git] / src / MacVim / RBSplitSubview.m
blob97748bfab38a4ac9f20a37d15f27a80f85224921
1 //
2 //  RBSplitSubview.m version 1.1.4
3 //  RBSplitView
4 //
5 //  Created by Rainer Brockerhoff on 19/11/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 // This variable points to the animation data structure while an animation is in
14 // progress; if there's none, it will be NULL. Animating may be very CPU-intensive so
15 // we allow only one animation to take place at a time.
16 static animationData* currentAnimation = NULL;
18 @implementation RBSplitSubview
20 // This class method returns YES if an animation is in progress.
21 + (BOOL)animating {
22         return currentAnimation!=NULL;
25 // This is the designated initializer for RBSplitSubview. It sets some reasonable defaults. However, you
26 // can't rely on anything working until you insert it into a RBSplitView.
27 - (id)initWithFrame:(NSRect)frame {
28     self = [super initWithFrame:frame];
29         if (self) {
30                 fraction = 0.0;
31                 canCollapse = NO;
32                 notInLimits = NO;
33                 minDimension = 1.0;
34                 maxDimension = WAYOUT;
35                 identifier = @"";
36                 previous = NSZeroRect;
37                 savedSize = frame.size;
38                 actDivider = NSNotFound;
39                 canDragWindow = NO;
40         }
41         return self;
44 // Just releases our stuff when going away.
45 - (void)dealloc {
46         [identifier release];
47         [super dealloc];
50 // These return nil since we're not a RBSplitView (they're overridden there).
51 - (RBSplitView*)asSplitView {
52         return nil;
55 - (RBSplitView*)coupledSplitView {
56         return nil;
59 // Sets and gets the coupling between a RBSplitView and its containing RBSplitView (if any).
60 // For convenience, these methods are also implemented here.
61 - (void)setCoupled:(BOOL)flag {
64 - (BOOL)isCoupled {
65         return NO;
68 // RBSplitSubviews are never flipped, unless they're RBSplitViews.
69 - (BOOL)isFlipped {
70         return NO;
73 // We copy the opacity of the owning split view.
74 - (BOOL)isOpaque {
75         return [[self couplingSplitView] isOpaque];
78 // A hidden RBSplitSubview is not redrawn and is not considered for drawing dividers.
79 // This won't work before 10.3, though.
80 - (void)setHidden:(BOOL)flag {
81         if ([self isHidden]!=flag) {
82                 RBSplitView* sv = [self splitView];
83                 [self RB___setHidden:flag];
84                 if (flag) {
85                         [sv adjustSubviews];
86                 } else {
87                         [sv adjustSubviewsExcepting:self];
88                 }
89         }
92 // RBSplitSubviews can't be in the responder chain.
93 - (BOOL)acceptsFirstResponder {
94         return NO;
97 // Mousing down should move the window only for a completely transparent background. This might have
98 // unintended side effects in metal windows, so for those you might want to use a background color
99 // with a very low alpha (0.01 for instance).
100 // This is commented out as I'm still experimenting with it.
101 /*- (BOOL)mouseDownCanMoveWindow {
102         RBSplitView* sv = [self asSplitView];
103         if (!sv) {
104                 sv = [self couplingSplitView];
105         }
106         return [sv background]==nil;
107         return YES;
110 // This returns the owning splitview. It's guaranteed to return a RBSplitView or nil.
111 // You should avoid having "orphan" RBSplitSubviews, or at least manipulating
112 // them while they're not inserted in a RBSplitView.
113 - (RBSplitView*)splitView {
114         id result = [self superview];
115         if ([result isKindOfClass:[RBSplitView class]]) {
116                 return (RBSplitView*)result;
117         }
118         return nil;
121 // This also returns the owning splitview. It's overridden for nested RBSplitViews.
122 - (RBSplitView*)couplingSplitView {
123         id result = [self superview];
124         if ([result isKindOfClass:[RBSplitView class]]) {
125                 return (RBSplitView*)result;
126         }
127         return nil;
130 // This returns the outermost directly containing RBSplitView, or nil.
131 - (RBSplitView*)outermostSplitView {
132         id result = nil;
133         id sv = self;
134         while ((sv = [sv superview])&&[sv isKindOfClass:[RBSplitView class]]) {
135                 result = sv;
136         }
137         return result;
140 // This convenience method returns YES if the containing RBSplitView is horizontal.
141 - (BOOL)splitViewIsHorizontal {
142         return [[self splitView] isHorizontal];
145 // You can use either tags (ints) or identifiers (NSStrings) to identify individual subviews.
146 // We take care not to have nil identifiers.
147 - (void)setTag:(int)theTag {
148         tag = theTag;
151 - (int)tag {
152         return tag;
155 - (void)setIdentifier:(NSString*)aString {
156         [identifier autorelease];
157         identifier = aString?[aString retain]:@"";
160 - (NSString*)identifier {
161         return identifier;
164 // If we have an identifier, this will make debugging a little easier by appending it to the
165 // default description.
166 - (NSString*)description {
167         return [identifier length]>0?[NSString stringWithFormat:@"%@(%@)",[super description],identifier]:[super description];
170 // This pair of methods allows you to get and change the position of a subview (within the split view);
171 // this counts from zero from the left or top of the split view.
172 - (unsigned)position {
173         RBSplitView* sv = [self splitView];
174         return sv?[[sv subviews] indexOfObjectIdenticalTo:self]:0;
177 - (void)setPosition:(unsigned)newPosition {
178         RBSplitView* sv = [self splitView];
179         if (sv) {
180                 [self retain];
181                 [self removeFromSuperviewWithoutNeedingDisplay];
182                 NSArray* subviews = [sv subviews];
183                 if (newPosition>=[subviews count]) {
184                         [sv addSubview:self positioned:NSWindowAbove relativeTo:nil];
185                 } else {
186                         [sv addSubview:self positioned:NSWindowBelow relativeTo:[subviews objectAtIndex:newPosition]];
187                 }
188                 [self release];
189         }
192 // Tests whether the subview is collapsed.
193 - (BOOL)isCollapsed {
194         return [self RB___visibleDimension]<=0.0;
197 // Tests whether the subview can shrink further.
198 - (BOOL)canShrink {
199         return [self RB___visibleDimension]>([self canCollapse]?0.0:minDimension);
202 // Tests whether the subview can expand further.
203 - (BOOL)canExpand {
204         return [self RB___visibleDimension]<maxDimension;
207 // Returns the subview's status.
208 - (RBSSubviewStatus)status {
209         animationData* anim = [self RB___animationData:NO resize:NO];
210         if (anim) {
211                 return anim->collapsing?RBSSubviewCollapsing:RBSSubviewExpanding;
212         }
213         return [self RB___visibleDimension]<=0.0?RBSSubviewCollapsed:RBSSubviewNormal;
216 // Tests whether the subview can be collapsed. The local instance variable will be overridden by the
217 // delegate method if it's implemented.
218 - (BOOL)canCollapse {
219         BOOL result = canCollapse;
220         RBSplitView* sv = [self splitView];
221         if ([sv RB___numberOfSubviews]<2) {
222                 return NO;
223         }
224         id delegate = [sv delegate];
225         if ([delegate respondsToSelector:@selector(splitView:canCollapse:)]) {
226                 result = [delegate splitView:sv canCollapse:self];
227         }
228         return result;
231 // This sets the subview's "canCollapse" flag. Ignored if the delegate's splitView:canCollapse:
232 // method is implemented.
233 - (void)setCanCollapse:(BOOL)flag {
234         canCollapse = flag;
237 // This expands a collapsed subview and calls the delegate's splitView:didExpand: method, if it exists.
238 // This is not called internally by other methods; call this to expand a subview programmatically.
239 // As a convenience to other methods, it returns the subview's dimension after expanding (this may be
240 // off by 1 pixel due to rounding) or 0.0 if it couldn't be expanded.
241 // The delegate should not change the subview's frame.
242 - (float)expand {
243         return [self RB___expandAndSetToMinimum:NO];
246 // This collapses an expanded subview and calls the delegate's splitView:didCollapse: method, if it exists.
247 // This is not called internally by other methods; call this to expand a subview programmatically.
248 // As a convenience to other methods, it returns the negative of the subview's dimension before
249 // collapsing (or 0.0 if it couldn't be collapsed).
250 // The delegate should not change the subview's frame.
251 - (float)collapse {
252         return [self RB___collapse];
255 // This tries to collapse the subview with animation, and collapses it instantly if some other
256 // subview is animating. Returns YES if animation was started successfully.
257 - (BOOL)collapseWithAnimation {
258         return [self collapseWithAnimation:YES withResize:YES];
261 // This tries to expand the subview with animation, and expands it instantly if some other
262 // subview is animating. Returns YES if animation was started successfully.
263 - (BOOL)expandWithAnimation {
264         return [self expandWithAnimation:YES withResize:YES];
267 // These methods collapse and expand subviews with animation, depending on the parameters.
268 // They return YES if animation startup was successful. If resize is NO, the subview is
269 // collapsed/expanded without resizing it during animation.
270 - (BOOL)collapseWithAnimation:(BOOL)animate withResize:(BOOL)resize {
271         if ([self status]==RBSSubviewNormal) {
272                 if ([self canCollapse]) {
273                         if (animate&&[self RB___animationData:YES resize:resize]) {
274                                 [self RB___clearResponder];
275                                 [self RB___stepAnimation];
276                                 return YES;
277                         } else {
278                                 [self RB___collapse];
279                         }
280                 }
281         }
282         return NO;
285 - (BOOL)expandWithAnimation:(BOOL)animate withResize:(BOOL)resize {
286         if ([self status]==RBSSubviewCollapsed) {
287                 if (animate&&[self RB___animationData:YES resize:resize]) {
288                         [self RB___stepAnimation];
289                         return YES;
290                 } else {
291                         [self RB___expandAndSetToMinimum:NO];
292                 }
293         }
294         return NO;
298 // These 3 methods get and set the view's minimum and maximum dimensions.
299 // The minimum dimension ought to be an integer at least equal to 1.0 but we make sure.
300 // The maximum dimension ought to be an integer at least equal to the minimum. As a convenience,
301 // pass in zero to set it to some huge number.
302 - (float)minDimension {
303         return minDimension;
306 - (float)maxDimension {
307         return maxDimension;
310 - (void)setMinDimension:(float)newMinDimension andMaxDimension:(float)newMaxDimension {
311         minDimension = MAX(1.0,floorf(newMinDimension));
312         if (newMaxDimension<1.0) {
313                 newMaxDimension = WAYOUT;
314         }
315         maxDimension = MAX(minDimension,floorf(newMaxDimension));
316         float dim = [self dimension];
317         if ((dim<minDimension)||(dim>maxDimension)) {
318                 [[self splitView] setMustAdjust];
319         }
322 // This returns the subview's dimension. If it's collapsed, it returns the dimension it would have
323 // after expanding.
324 - (float)dimension {
325         float dim = [self RB___visibleDimension];
326         if (dim<=0.0) {
327                 dim = [[self splitView] RB___dimensionWithoutDividers]*fraction;
328                 if (dim<minDimension) {
329                         dim = minDimension;
330                 } else if (dim>maxDimension) {
331                         dim = maxDimension;
332                 }
333         }
334         return dim;
337 // Sets the current dimension of the subview, subject to the current maximum and minimum.
338 // If the subview is collapsed, this will have an effect only after reexpanding.
339 - (void)setDimension:(float)value {
340         RBSplitView* sv = [self splitView];
341         NSSize size = [self frame].size;
342         BOOL ishor = [sv isHorizontal];
343         if (DIM(size)>0.0) {
344 // We're not collapsed, set the size and adjust other subviews.
345                 DIM(size) = value;
346                 [self setFrameSize:size];
347                 [sv adjustSubviewsExcepting:self];
348         } else {
349 // We're collapsed, adjust the fraction so that we'll have the (approximately) correct
350 // dimension after expanding.
351                 fraction = value/[sv RB___dimensionWithoutDividers];
352         }
355 // This just draws the background of a subview, then tells the delegate, if any.
356 // The delegate would usually draw a frame inside the subview.
357 - (void)drawRect:(NSRect)rect {
358         RBSplitView* sv = [self splitView];
359         NSColor* bg = [sv background];
360         if (bg) {
361                 [bg set];
362                 NSRectFillUsingOperation(rect,NSCompositeSourceOver);
363         }
364         id del = [sv delegate];
365         if ([del respondsToSelector:@selector(splitView:willDrawSubview:inRect:)]) {
366                 [del splitView:sv willDrawSubview:self inRect:rect];
367         }
370 // We check if the RBSplitView must be adjusted before redisplaying programmatically.
371 // if so, we adjust and display the whole RBSplitView.
372 - (void)display {
373         RBSplitView* sv = [self splitView];
374         if (sv) {
375                 if ([sv mustAdjust]) {
376                         [sv display];
377                 } else {
378                         [super display];
379                 }
380         }
383 // RBSplitSubviews will always resize their own subviews.
384 - (BOOL)autoresizesSubviews {
385         return YES;
388 // This is method is called automatically when the subview is resized; don't call it yourself.
389 - (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize {
390         RBSplitView* sv = [self splitView];
391         if (sv) {
392                 BOOL ishor = [sv isHorizontal];
393                 NSRect frame = [self frame];
394                 float dim = DIM(frame.size);
395                 float other = OTHER(frame.size);
396 // We resize subviews only when we're inside the subview's limits and the containing splitview's limits.
397                 animationData* anim = [self RB___animationData:NO resize:NO];
398                 if ((dim>=(anim&&!anim->resizing?anim->dimension:minDimension))&&(dim<=maxDimension)&&(other>=[sv minDimension])&&(other<=[sv maxDimension])) {
399                         if (notInLimits) {
400 // The subviews can be resized, so we restore the saved size.
401                                 oldBoundsSize = savedSize;
402                         }
403 // We save the size every time the subview's subviews are resized within the limits.
404                         notInLimits = NO;
405                         savedSize = frame.size;
406                         [super resizeSubviewsWithOldSize:oldBoundsSize];
407                 } else {
408                         notInLimits = YES;
409                 }
410         }
413 // This method is used internally when a divider is dragged. It tries to change the subview's dimension
414 // and returns the actual change, collapsing or expanding whenever possible. You usually won't need
415 // to call this directly.
416 - (float)changeDimensionBy:(float)increment mayCollapse:(BOOL)mayCollapse move:(BOOL)move {
417         RBSplitView* sv = [self splitView];
418         if (!sv||(fabsf(increment)<1.0)) {
419                 return 0.0;
420         }
421         BOOL ishor = [sv isHorizontal];
422         NSRect frame = [self frame];
423         float olddim = DIM(frame.size);
424         float newdim = MAX(0.0,olddim+increment);
425         if (newdim<olddim) {
426                 if (newdim<minDimension) {
427 // Collapse if needed
428                         if (mayCollapse&&[self canCollapse]&&(newdim<MAX(1.0,minDimension*(0.5-HYSTERESIS)))) {
429                                 return [self RB___collapse];
430                         }
431                         newdim = minDimension;
432                 }
433         } else if (newdim>olddim) {
434                 if (olddim<1.0) {
435 // Expand if needed.
436                         if (newdim>(minDimension*(0.5+HYSTERESIS))) {
437                                 newdim = MAX(newdim,[self RB___expandAndSetToMinimum:YES]);
438                         } else {
439                                 return 0.0;
440                         }
441                 }
442                 if (newdim>maxDimension) {
443                         newdim = maxDimension;
444                 }
445         }
446         if ((int)newdim!=(int)olddim) {
447 // The dimension has changed.
448                 increment = newdim-olddim;
449                 DIM(frame.size) = newdim;
450                 if (move) {
451                         DIM(frame.origin) -= increment;
452                 }
453 // We call super instead of self here to postpone adjusting subviews for nested splitviews.
454 //              [super setFrameSize:frame.size];
455                 [super setFrame:frame];
456                 [sv RB___setMustClearFractions];
457                 [sv setMustAdjust];
458         }
459         return newdim-olddim;
462 // This convenience method returns the number of subviews (surprise!)
463 - (unsigned)numberOfSubviews {
464         return [[self subviews] count];
467 // We return the deepest subview that's hit by aPoint. We also check with the delegate if aPoint is
468 // within an alternate drag view.
469 - (NSView*)hitTest:(NSPoint)aPoint {
470         RBSplitView* sv = [self splitView];
471         if ([self mouse:aPoint inRect:[self frame]]) {
472                 id delegate = [sv delegate];
473                 if ([delegate respondsToSelector:@selector(splitView:dividerForPoint:inSubview:)]) {
474                         actDivider = [delegate splitView:sv dividerForPoint:aPoint inSubview:self];
475                         if ((int)actDivider<(int)([sv RB___numberOfSubviews]-1)) {
476                                 return self;
477                         }
478                 }
479                 actDivider = NSNotFound;
480                 NSView* result = [super hitTest:aPoint];
481                 canDragWindow = ![result isOpaque];
482                 return result;
483         }
484         return nil;
487 // This method handles clicking and dragging in an empty portion of the subview, or in an alternate
488 // drag view as designated by the delegate.
489 - (void)mouseDown:(NSEvent*)theEvent {
490         NSWindow* window = [self window];
491         NSPoint where = [theEvent locationInWindow];
492         if (actDivider<NSNotFound) {
493 // The mouse down was inside an alternate drag view; actDivider was just set in hitTest.
494                 RBSplitView* sv = [self splitView];
495                 NSPoint point = [sv convertPoint:where fromView:nil];
496                 [[RBSplitView cursor:RBSVDragCursor] push];
497                 NSPoint base = NSZeroPoint;
498 // Record the current divider coordinate.
499                 float divc = [sv RB___dividerOrigin:actDivider];
500                 BOOL ishor = [sv isHorizontal];
501                 [sv RB___setDragging:YES];
502 // Loop while the button is down.
503                 while ((theEvent = [NSApp nextEventMatchingMask:NSLeftMouseDownMask|NSLeftMouseDraggedMask|NSLeftMouseUpMask untilDate:[NSDate distantFuture] inMode:NSEventTrackingRunLoopMode dequeue:YES])&&([theEvent type]!=NSLeftMouseUp)) {
504 // Set up a local autorelease pool for the loop to prevent buildup of temporary objects.
505                         NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
506                         NSDisableScreenUpdates();
507 // This does the actual movement.
508                         [sv RB___trackMouseEvent:theEvent from:point withBase:base inDivider:actDivider];
509                         if ([sv mustAdjust]) {
510 // If something changed, we clear fractions and redisplay.
511                                 [sv RB___setMustClearFractions];
512                                 [sv display];
513                         }
514 // Change the drag point by the actual amount moved.
515                         float newc = [sv RB___dividerOrigin:actDivider];
516                         DIM(point) += newc-divc;
517                         divc = newc;
518                         NSEnableScreenUpdates();
519                         [pool release];
520                 }
521                 [sv RB___setDragging:NO];
522                 [NSCursor pop];
523                 actDivider = NSNotFound;
524                 return;
525         }
526         if (canDragWindow&&[window isMovableByWindowBackground]&&![[self couplingSplitView] background]) {
527 // If we get here, it's a textured (metal) window, the mouse has gone down on an non-opaque portion
528 // of the subview, and our RBSplitView has a transparent background. RBSplitView returns NO to
529 // mouseDownCanMoveWindow, but the window should move here - after all, the window background
530 // is visible right here! So we fake it and move the window as intended. Mwahahaha!
531                 where =  [window convertBaseToScreen:where];
532                 NSPoint origin = [window frame].origin;
533 // Now we loop handling mouse events until we get a mouse up event.
534                 while ((theEvent = [NSApp nextEventMatchingMask:NSLeftMouseDownMask|NSLeftMouseDraggedMask|NSLeftMouseUpMask untilDate:[NSDate distantFuture] inMode:NSEventTrackingRunLoopMode dequeue:YES])&&([theEvent type]!=NSLeftMouseUp)) {
535 // Set up a local autorelease pool for the loop to prevent buildup of temporary objects.
536                         NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
537                         NSPoint now = [window convertBaseToScreen:[theEvent locationInWindow]];
538                         origin.x += now.x-where.x;
539                         origin.y += now.y-where.y;
540 // Move the window by the mouse displacement since the last event.
541                         [window setFrameOrigin:origin];
542                         where = now;
543                         [pool release];
544                 }
545         }
548 // These two methods encode and decode subviews.
549 - (void)encodeWithCoder:(NSCoder*)coder {
550         NSRect frame;
551         BOOL coll = [self isCollapsed];
552         if (coll) {
553 // We can't encode a collapsed subview as-is, so we correct the frame size first and add WAYOUT
554 // to the origin to signal it was collapsed. 
555                 NSRect newf = frame = [self frame];
556                 newf.origin.x += WAYOUT;
557                 [super setFrameOrigin:newf.origin];
558                 newf.size = savedSize;
559                 [super setFrameSize:newf.size];
560         }
561         [super encodeWithCoder:coder];
562         if (coll) {
563                 [super setFrame:frame];
564         }
565         if ([coder allowsKeyedCoding]) {
566                 [coder encodeObject:identifier forKey:@"identifier"];
567                 [coder encodeInt:tag forKey:@"tag"];
568                 [coder encodeFloat:minDimension forKey:@"minDimension"];
569                 [coder encodeFloat:maxDimension forKey:@"maxDimension"];
570                 [coder encodeDouble:fraction forKey:@"fraction"];
571                 [coder encodeBool:canCollapse forKey:@"canCollapse"];
572         } else {
573                 [coder encodeObject:identifier];
574                 [coder encodeValueOfObjCType:@encode(typeof(tag)) at:&tag];
575                 [coder encodeValueOfObjCType:@encode(typeof(minDimension)) at:&minDimension];
576                 [coder encodeValueOfObjCType:@encode(typeof(maxDimension)) at:&maxDimension];
577                 [coder encodeValueOfObjCType:@encode(typeof(fraction)) at:&fraction];
578                 [coder encodeValueOfObjCType:@encode(typeof(canCollapse)) at:&canCollapse];
579         }
582 - (id)initWithCoder:(NSCoder*)coder {
583     if ((self = [super initWithCoder:coder])) {
584                 fraction = 0.0;
585                 canCollapse = NO;
586                 notInLimits = NO;
587                 minDimension = 1.0;
588                 maxDimension = WAYOUT;
589                 identifier = @"";
590                 actDivider = NSNotFound;
591                 canDragWindow = NO;
592                 previous = [self frame];
593                 savedSize = previous.size;
594                 if (previous.origin.x>=WAYOUT) {
595 // The subview was collapsed when encoded, so we correct the origin and collapse it.
596                         BOOL ishor = [self splitViewIsHorizontal];
597                         previous.origin.x -= WAYOUT;
598                         DIM(previous.size) = 0.0;
599                         [self setFrameOrigin:previous.origin];
600                         [self setFrameSize:previous.size];
601                 }
602                 previous = NSZeroRect;
603                 if ([coder allowsKeyedCoding]) {
604                         [self setIdentifier:[coder decodeObjectForKey:@"identifier"]];
605                         tag = [coder decodeIntForKey:@"tag"];
606                         minDimension = [coder decodeFloatForKey:@"minDimension"];
607                         maxDimension = [coder decodeFloatForKey:@"maxDimension"];
608                         fraction = [coder decodeDoubleForKey:@"fraction"];
609                         canCollapse = [coder decodeBoolForKey:@"canCollapse"];
610                 } else {
611                         [self setIdentifier:[coder decodeObject]];
612                         [coder decodeValueOfObjCType:@encode(typeof(tag)) at:&tag];
613                         [coder decodeValueOfObjCType:@encode(typeof(minDimension)) at:&minDimension];
614                         [coder decodeValueOfObjCType:@encode(typeof(maxDimension)) at:&maxDimension];
615                         [coder decodeValueOfObjCType:@encode(typeof(fraction)) at:&fraction];
616                         [coder decodeValueOfObjCType:@encode(typeof(canCollapse)) at:&canCollapse];
617                 }
618         }
619     return self;
622 @end
624 @implementation RBSplitSubview (RB___SubviewAdditions)
626 // This hides/shows the subview without calling adjustSubview.
627 - (void)RB___setHidden:(BOOL)flag {
628         [super setHidden:flag];
631 // This internal method returns the current animationData. It will always return nil if
632 // the receiver isn't the current owner and some other subview is already being animated.
633 // Otherwise, if the parameter is YES, a new animation will be started (or the current
634 // one will be restarted).
635 - (animationData*)RB___animationData:(BOOL)start resize:(BOOL)resize {
636         if (currentAnimation&&(currentAnimation->owner!=self)) {
637 // There already is an animation in progress on some other subview.
638                 return nil;
639         }
640         if (start) {
641 // We want to start (or restart) an animation.
642                 RBSplitView* sv = [self splitView];
643                 if (sv) {
644                         float dim = [self dimension];
645 // First assume the default time, then ask the delegate.
646                         NSTimeInterval total = dim*(0.2/150.0);
647                         id delegate = [sv delegate];
648                         if ([delegate respondsToSelector:@selector(splitView:willAnimateSubview:withDimension:)]) {
649                                 total = [delegate splitView:sv willAnimateSubview:self withDimension:dim];
650                         }
651 // No use animating anything shorter than the frametime.
652                         if (total>FRAMETIME) {
653                                 if (!currentAnimation) {
654                                         currentAnimation = (animationData*)malloc(sizeof(animationData));
655                                 }
656                                 if (currentAnimation) {
657                                         currentAnimation->owner = self;
658                                         currentAnimation->stepsDone = 0;
659                                         currentAnimation->elapsedTime = 0.0;
660                                         currentAnimation->dimension = dim;
661                                         currentAnimation->collapsing = ![self isCollapsed];
662                                         currentAnimation->totalTime = total;
663                                         currentAnimation->finishTime = [NSDate timeIntervalSinceReferenceDate]+total;
664                                         currentAnimation->resizing = resize;
665                                         [sv RB___setDragging:YES];
666                                 }
667                         } else if (currentAnimation) {
668                                 free(currentAnimation);
669                                 currentAnimation = NULL;
670                         }
671                 }
672         }
673         return currentAnimation;
676 // This internal method steps the animation to the next frame.
677 - (void)RB___stepAnimation {
678         NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
679         animationData* anim = [self RB___animationData:NO resize:NO];
680         if (anim) {
681                 RBSplitView* sv = [self splitView];
682                 NSTimeInterval remain = anim->finishTime-now;
683                 NSRect frame = [self frame];
684                 BOOL ishor = [sv isHorizontal];
685 // Continuing animation only makes sense if we still have at least FRAMETIME available.
686                 if (remain>=FRAMETIME) {
687                         float dim = DIM(frame.size);
688                         float avg = anim->elapsedTime;
689 // We try to keep a record of how long it takes, on the average, to resize and adjust
690 // one animation frame.
691                         if (anim->stepsDone) {
692                                 avg /= anim->stepsDone;
693                         }
694                         NSTimeInterval delay = MIN(0.0,FRAMETIME-avg);
695 // We adjust the new dimension proportionally to how much of the designated time has passed.
696                         dim = floorf(anim->dimension*(remain-avg)/anim->totalTime);
697                         if (dim>4.0) {
698                                 if (!anim->collapsing) {
699                                         dim = anim->dimension-dim;
700                                 } 
701                                 DIM(frame.size) = dim;
702                                 [self RB___setFrame:frame withFraction:0.0 notify:NO];
703                                 [sv adjustSubviews];
704                                 [self display];
705                                 anim->elapsedTime += [NSDate timeIntervalSinceReferenceDate]-now;
706                                 ++anim->stepsDone;
707 // Schedule a timer to do the next animation step.
708                                 [self performSelector:@selector(RB___stepAnimation) withObject:nil afterDelay:delay inModes:[NSArray arrayWithObjects:NSDefaultRunLoopMode,NSModalPanelRunLoopMode,
709                                         NSEventTrackingRunLoopMode,nil]];
710                                 return;
711                         }
712                 }
713 // We're finished, either collapse or expand entirely now.
714                 if (anim->collapsing) {
715                         DIM(frame.size) = 0.0;
716                         [self RB___finishCollapse:frame withFraction:anim->dimension/[sv RB___dimensionWithoutDividers]];
717                 } else {
718                         float savemin,savemax;
719                         float dim = [self RB___setMinAndMaxTo:anim->dimension savingMin:&savemin andMax:&savemax];
720                         DIM(frame.size) = dim;
721                         [self RB___finishExpand:frame withFraction:0.0];
722                         minDimension = savemin;
723                         maxDimension = savemax;
724                 }
725         }
728 // This internal method stops the animation, if the receiver is being animated. It will
729 // return YES if the animation was stopped.
730 - (BOOL)RB___stopAnimation {
731         if (currentAnimation&&(currentAnimation->owner==self)) {
732                 [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(RB___stepAnimation) object:nil];
733                 free(currentAnimation);
734                 currentAnimation = NULL;
735                 [[self splitView] RB___setDragging:NO];
736                 return YES;
737         }
738         return NO;
741 // This internal method returns the actual visible dimension of the subview. Differs from -dimension in
742 // that it returns 0.0 if the subview is collapsed.
743 - (float)RB___visibleDimension {
744         BOOL ishor = [self splitViewIsHorizontal];
745         NSRect frame = [self frame];
746         return MAX(0.0,DIM(frame.size));
749 // This pair of internal methods is used only inside -[RBSplitView adjustSubviews] to copy subview data
750 // from and to that method's internal cache.
751 - (void)RB___copyIntoCache:(subviewCache*)cache {
752         cache->sub = self;
753         cache->rect = [self frame];
754         cache->size = [self RB___visibleDimension];
755         cache->fraction = fraction;
756         cache->constrain = NO;
759 - (void)RB___updateFromCache:(subviewCache*)cache withTotalDimension:(float)value {
760         float dim = [self RB___visibleDimension];
761         if (cache->size>=1.0) {
762 // New state is not collapsed.
763                 if (dim>=1.0) {
764 // Old state was not collapsed, so we just change the frame.
765                         [self RB___setFrame:cache->rect withFraction:cache->fraction notify:YES];
766                 } else {
767 // Old state was collapsed, so we expand it.
768                         [self RB___finishExpand:cache->rect withFraction:cache->fraction];
769                 }
770         } else {
771 // New state is collapsed.
772                 if (dim>=1.0) {
773 // Old state was not collapsed, so we clear first responder and change the frame.
774                         [self RB___clearResponder];
775                         [self RB___finishCollapse:cache->rect withFraction:dim/value];
776                 } else {
777 // It was collapsed already, but the frame may have changed, so we set it.
778                         [self RB___setFrame:cache->rect withFraction:cache->fraction notify:YES];
779                 }
780         }
783 // This internal method sets minimum and maximum values to the same value, saves the old values,
784 // and returns the new value (which will be limited to the old values).
785 - (float)RB___setMinAndMaxTo:(float)value savingMin:(float*)oldmin andMax:(float*)oldmax {
786         *oldmin = [self minDimension];
787         *oldmax = [self maxDimension];
788         if (value<*oldmin) {
789                 value = *oldmin;
790         }
791         if (value>*oldmax) {
792                 value = *oldmax;
793         }
794         minDimension = maxDimension = value;
795         return value;
798 // This internal method tries to clear the first responder, if the current responder is a descendant of
799 // the receiving subview. If so, it will set first responder to nil, redisplay the former responder and
800 // return YES. Returns NO otherwise.
801 - (BOOL)RB___clearResponder {
802         NSWindow* window = [self window];
803         if (window) {
804                 NSView* responder = (NSView*)[window firstResponder];
805                 if (responder&&[responder respondsToSelector:@selector(isDescendantOf:)]) {
806                         if ([responder isDescendantOf:self]) {
807                                 if ([window makeFirstResponder:nil]) {
808                                         [responder display];
809                                         return YES;
810                                 }
811                         }
812                 }
813         }
814         return NO;
817 // This internal method collapses a subview.
818 // It returns the negative of the size of the subview before collapsing, or 0.0 if it wasn't collapsed.
819 - (float)RB___collapse {
820         float result = 0.0;
821         if (![self isCollapsed]) {
822                 RBSplitView* sv = [self splitView];
823                 if (sv&&[self canCollapse]) {
824                         [self RB___clearResponder];
825                         NSRect frame = [self frame];
826                         BOOL ishor = [sv isHorizontal];
827                         result = DIM(frame.size);
828 // For collapsed views, fraction will contain the fraction of the dimension previously occupied
829                         DIM(frame.size) = 0.0;
830                         [self RB___finishCollapse:frame withFraction:result/[sv RB___dimensionWithoutDividers]];
831                 }
832         }
833         return -result;
836 // This internal method finishes the collapse of a subview, stopping the animation if
837 // there is one, and calling the delegate method if there is one.
838 - (void)RB___finishCollapse:(NSRect)rect withFraction:(double)value {
839         RBSplitView* sv = [self splitView];
840         BOOL finish = [self RB___stopAnimation];
841         [self RB___setFrame:rect withFraction:value notify:YES];
842         [sv RB___setMustClearFractions];
843         if (finish) {
844                 [self display];
845         }
846         id delegate = [sv delegate];
847         if ([delegate respondsToSelector:@selector(splitView:didCollapse:)]) {
848                 [delegate splitView:sv didCollapse:self];
849         }
852 // This internal method expands a subview. setToMinimum will usually be YES during a divider drag.
853 // It returns the size of the subview after expanding, or 0.0 if it wasn't expanded.
854 - (float)RB___expandAndSetToMinimum:(BOOL)setToMinimum {
855         float result = 0.0;
856         RBSplitView* sv = [self splitView];
857         if (sv&&[self isCollapsed]) {
858                 NSRect frame = [super frame];
859                 double frac = fraction;
860                 BOOL ishor = [sv isHorizontal];
861                 if (setToMinimum) {
862                         result = DIM(frame.size) = minDimension;
863                 } else {
864                         result = [sv RB___dimensionWithoutDividers]*frac;
865 // We need to apply a compensation factor for proportional resizing in adjustSubviews.
866                         float newdim = floorf((frac>=1.0)?result:result/(1.0-frac));
867                         DIM(frame.size) = newdim;
868                         result = floorf(result);
869                 }
870                 [self RB___finishExpand:frame withFraction:0.0];
871         }
872         return result;
875 // This internal method finishes the the expansion of a subview, stopping the animation if
876 // there is one, and calling the delegate method if there is one.
877 - (void)RB___finishExpand:(NSRect)rect withFraction:(double)value {
878         RBSplitView* sv = [self splitView];
879         BOOL finish = [self RB___stopAnimation];
880         [self RB___setFrame:rect withFraction:value notify:YES];
881         [sv RB___setMustClearFractions];
882         if (finish) {
883                 [self display];
884         }
885         id delegate = [sv delegate];
886         if ([delegate respondsToSelector:@selector(splitView:didExpand:)]) {
887                 [delegate splitView:sv didExpand:self];
888         }
891 // These internal methods set the subview's frame or size, and also store a fraction value
892 // which is used to ensure repeatability when the whole split view is resized.
893 - (void)RB___setFrame:(NSRect)rect withFraction:(double)value notify:(BOOL)notify {
894         RBSplitView* sv = [self splitView];
895         id delegate = nil;
896         if (notify) {
897                 delegate = [sv delegate];
898 // If the delegate method isn't implemented, we ignore the delegate altogether.
899                 if ([delegate respondsToSelector:@selector(splitView:changedFrameOfSubview:from:to:)]) {
900 // If the rects are equal, the delegate isn't called.
901                         if (NSEqualRects(previous,rect)) {
902                                 delegate = nil;
903                         }
904                 } else {
905                         delegate = nil;
906                 }
907         }
908         [sv setMustAdjust];
909         [self setFrame:rect];
910         fraction = value;
911         [delegate splitView:sv changedFrameOfSubview:self from:previous to:rect];
912         previous = delegate?rect:NSZeroRect;
915 - (void)RB___setFrameSize:(NSSize)size withFraction:(double)value {
916         [[self splitView] setMustAdjust];
917         [self setFrameSize:size];
918         fraction = value;
921 // This internal method gets the fraction value.
922 - (double)RB___fraction {
923         return fraction;
926 @end