2
// RBSplitSubview.m version 1.2
5
// Created by Rainer Brockerhoff on 19/11/2004.
6
// Copyright 2004-2009 Rainer Brockerhoff.
7
// Some Rights Reserved under the Creative Commons Attribution License, version 2.5, and/or the MIT License.
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.
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];
34
maxDimension = WAYOUT;
36
previous = NSZeroRect;
37
savedSize = frame.size;
38
actDivider = NSNotFound;
44
// Just releases our stuff when going away.
50
// These return nil since we're not a RBSplitView (they're overridden there).
51
- (RBSplitView*)asSplitView {
55
- (RBSplitView*)coupledSplitView {
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 {
68
// RBSplitSubviews are never flipped, unless they're RBSplitViews.
73
// We copy the opacity of the owning split view.
75
return [[self couplingSplitView] isOpaque];
78
// A hidden RBSplitSubview is not redrawn and is not considered for drawing dividers.
79
- (void)setHidden:(BOOL)flag {
80
if ([self isHidden]!=flag) {
81
RBSplitView* sv = [self splitView];
82
[self RB___setHidden:flag];
86
[sv adjustSubviewsExcepting:self];
91
// RBSplitSubviews can't be in the responder chain.
92
- (BOOL)acceptsFirstResponder {
96
// This returns the owning splitview. It's guaranteed to return a RBSplitView or nil.
97
// You should avoid having "orphan" RBSplitSubviews, or at least manipulating
98
// them while they're not inserted in a RBSplitView.
99
- (RBSplitView*)splitView {
100
id result = [self superview];
101
if ([result isKindOfClass:[RBSplitView class]]) {
102
return (RBSplitView*)result;
107
// This also returns the owning splitview. It's overridden for nested RBSplitViews.
108
- (RBSplitView*)couplingSplitView {
109
id result = [self superview];
110
if ([result isKindOfClass:[RBSplitView class]]) {
111
return (RBSplitView*)result;
116
// This returns the outermost directly containing RBSplitView, or nil.
117
- (RBSplitView*)outermostSplitView {
120
while ((sv = [sv superview])&&[sv isKindOfClass:[RBSplitView class]]) {
126
// This convenience method returns YES if the containing RBSplitView is horizontal.
127
- (BOOL)splitViewIsHorizontal {
128
return [[self splitView] isHorizontal];
131
// You can use either tags (NSIntegers) or identifiers (NSStrings) to identify individual subviews.
132
// We take care not to have nil identifiers.
133
- (void)setTag:(NSInteger)theTag {
141
- (void)setIdentifier:(NSString*)aString {
142
[identifier autorelease];
143
identifier = aString?[aString retain]:@"";
146
- (NSString*)identifier {
150
// If we have an identifier, this will make debugging a little easier by appending it to the
151
// default description.
152
- (NSString*)description {
153
return [identifier length]>0?[NSString stringWithFormat:@"%@(%@)",[super description],identifier]:[super description];
156
// This pair of methods allows you to get and change the position of a subview (within the split view);
157
// this counts from zero from the left or top of the split view.
158
- (NSUInteger)position {
159
RBSplitView* sv = [self splitView];
160
return sv?[[sv subviews] indexOfObjectIdenticalTo:self]:0;
163
- (void)setPosition:(NSUInteger)newPosition {
164
RBSplitView* sv = [self splitView];
167
[self removeFromSuperviewWithoutNeedingDisplay];
168
NSArray* subviews = [sv subviews];
169
if (newPosition>=[subviews count]) {
170
[sv addSubview:self positioned:NSWindowAbove relativeTo:nil];
172
[sv addSubview:self positioned:NSWindowBelow relativeTo:[subviews objectAtIndex:newPosition]];
178
// Tests whether the subview is collapsed.
179
- (BOOL)isCollapsed {
180
return [self RB___visibleDimension]<=0.0;
183
// Tests whether the subview can shrink further.
185
return [self RB___visibleDimension]>([self canCollapse]?0.0:minDimension);
188
// Tests whether the subview can expand further.
190
return [self RB___visibleDimension]<maxDimension;
193
// Returns the subview's status.
194
- (RBSSubviewStatus)status {
195
animationData* anim = [self RB___animationData:NO resize:NO];
197
return anim->collapsing?RBSSubviewCollapsing:RBSSubviewExpanding;
199
return [self RB___visibleDimension]<=0.0?RBSSubviewCollapsed:RBSSubviewNormal;
202
// Tests whether the subview can be collapsed. The local instance variable will be overridden by the
203
// delegate method if it's implemented.
204
- (BOOL)canCollapse {
205
BOOL result = canCollapse;
206
RBSplitView* sv = [self splitView];
207
if ([sv RB___numberOfSubviews]<2) {
210
id delegate = [sv delegate];
211
if ([delegate respondsToSelector:@selector(splitView:canCollapse:)]) {
212
result = [delegate splitView:sv canCollapse:self];
217
// This sets the subview's "canCollapse" flag. Ignored if the delegate's splitView:canCollapse:
218
// method is implemented.
219
- (void)setCanCollapse:(BOOL)flag {
223
// This expands a collapsed subview and calls the delegate's splitView:didExpand: method, if it exists.
224
// This is not called internally by other methods; call this to expand a subview programmatically.
225
// As a convenience to other methods, it returns the subview's dimension after expanding (this may be
226
// off by 1 pixel due to rounding) or 0.0 if it couldn't be expanded.
227
// The delegate should not change the subview's frame.
229
return [self RB___expandAndSetToMinimum:NO];
232
// This collapses an expanded subview and calls the delegate's splitView:didCollapse: method, if it exists.
233
// This is not called internally by other methods; call this to expand a subview programmatically.
234
// As a convenience to other methods, it returns the negative of the subview's dimension before
235
// collapsing (or 0.0 if it couldn't be collapsed).
236
// The delegate should not change the subview's frame.
237
- (CGFloat)collapse {
238
return [self RB___collapse];
241
// This tries to collapse the subview with animation, and collapses it instantly if some other
242
// subview is animating. Returns YES if animation was started successfully.
243
- (BOOL)collapseWithAnimation {
244
return [self collapseWithAnimation:YES withResize:YES];
247
// This tries to expand the subview with animation, and expands it instantly if some other
248
// subview is animating. Returns YES if animation was started successfully.
249
- (BOOL)expandWithAnimation {
250
return [self expandWithAnimation:YES withResize:YES];
253
// These methods collapse and expand subviews with animation, depending on the parameters.
254
// They return YES if animation startup was successful. If resize is NO, the subview is
255
// collapsed/expanded without resizing it during animation.
256
- (BOOL)collapseWithAnimation:(BOOL)animate withResize:(BOOL)resize {
257
if ([self status]==RBSSubviewNormal) {
258
if ([self canCollapse]) {
259
if (animate&&[self RB___animationData:YES resize:resize]) {
260
[self RB___clearResponder];
261
[self RB___stepAnimation];
264
[self RB___collapse];
271
- (BOOL)expandWithAnimation:(BOOL)animate withResize:(BOOL)resize {
272
if ([self status]==RBSSubviewCollapsed) {
273
if (animate&&[self RB___animationData:YES resize:resize]) {
274
[self RB___stepAnimation];
277
[self RB___expandAndSetToMinimum:NO];
283
// These 3 methods get and set the view's minimum and maximum dimensions.
284
// The minimum dimension ought to be an integer at least equal to 1.0 but we make sure.
285
// The maximum dimension ought to be an integer at least equal to the minimum. As a convenience,
286
// pass in zero to set it to some huge number.
287
- (CGFloat)minDimension {
291
- (CGFloat)maxDimension {
295
- (void)setMinDimension:(CGFloat)newMinDimension andMaxDimension:(CGFloat)newMaxDimension {
296
minDimension = MAX(1.0,floor(newMinDimension));
297
if (newMaxDimension<1.0) {
298
newMaxDimension = WAYOUT;
300
maxDimension = MAX(minDimension,floor(newMaxDimension));
301
CGFloat dim = [self dimension];
302
if ((dim<minDimension)||(dim>maxDimension)) {
303
[[self splitView] setMustAdjust];
307
// This returns the subview's dimension. If it's collapsed, it returns the dimension it would have
309
- (CGFloat)dimension {
310
CGFloat dim = [self RB___visibleDimension];
312
dim = [[self splitView] RB___dimensionWithoutDividers]*fraction;
313
if (dim<minDimension) {
315
} else if (dim>maxDimension) {
322
// Sets the current dimension of the subview, subject to the current maximum and minimum.
323
// If the subview is collapsed, this will have an effect only after reexpanding.
324
- (void)setDimension:(CGFloat)value {
325
if (value<minDimension) {
326
value = minDimension;
327
} else if (value>maxDimension) {
328
value = maxDimension;
330
RBSplitView* sv = [self splitView];
331
NSSize size = [self frame].size;
332
BOOL ishor = [sv isHorizontal];
334
// We're not collapsed, set the size and adjust other subviews.
336
[self setFrameSize:size];
337
[sv adjustSubviewsExcepting:self];
339
// We're collapsed, adjust the fraction so that we'll have the (approximately) correct
340
// dimension after expanding.
341
fraction = value/[sv RB___dimensionWithoutDividers];
345
// This just draws the background of a subview, then tells the delegate, if any.
346
// The delegate would usually draw a frame inside the subview.
347
- (void)drawRect:(NSRect)rect {
348
RBSplitView* sv = [self splitView];
349
NSColor* bg = [sv background];
352
NSRectFillUsingOperation(rect,NSCompositeSourceOver);
354
id del = [sv delegate];
355
if ([del respondsToSelector:@selector(splitView:willDrawSubview:inRect:)]) {
356
[del splitView:sv willDrawSubview:self inRect:rect];
360
// We check if the RBSplitView must be adjusted before redisplaying programmatically.
361
// if so, we adjust and display the whole RBSplitView.
363
RBSplitView* sv = [self splitView];
365
if ([sv mustAdjust]) {
373
// RBSplitSubviews will always resize their own subviews.
374
- (BOOL)autoresizesSubviews {
378
// ...and we don't want that to change under any circumstances.
379
- (void)setAutoresizesSubviews:(BOOL)flag {
382
// This is method is called automatically when the subview is resized; don't call it yourself.
383
- (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize {
384
RBSplitView* sv = [self splitView];
386
BOOL ishor = [sv isHorizontal];
387
NSRect frame = [self frame];
388
CGFloat dim = DIM(frame.size);
389
CGFloat other = OTHER(frame.size);
390
// We resize subviews only when we're inside the subview's limits and the containing splitview's limits.
391
animationData* anim = [self RB___animationData:NO resize:NO];
392
if ((dim>=(anim&&!anim->resizing?anim->dimension:minDimension))&&(dim<=maxDimension)&&(other>=[sv minDimension])&&(other<=[sv maxDimension])) {
394
// The subviews can be resized, so we restore the saved size.
395
oldBoundsSize = savedSize;
397
// We save the size every time the subview's subviews are resized within the limits.
399
savedSize = frame.size;
400
[super resizeSubviewsWithOldSize:oldBoundsSize];
407
// This method is used internally when a divider is dragged. It tries to change the subview's dimension
408
// and returns the actual change, collapsing or expanding whenever possible. You usually won't need
409
// to call this directly.
410
- (CGFloat)changeDimensionBy:(CGFloat)increment mayCollapse:(BOOL)mayCollapse move:(BOOL)move {
411
RBSplitView* sv = [self splitView];
412
if (!sv||(fabs(increment)<1.0)) {
415
BOOL ishor = [sv isHorizontal];
416
NSRect frame = [self frame];
417
CGFloat olddim = DIM(frame.size);
418
CGFloat newdim = MAX(0.0,olddim+increment);
420
if (newdim<minDimension) {
421
// Collapse if needed
422
if (mayCollapse&&[self canCollapse]&&(newdim<MAX(1.0,minDimension*(0.5-HYSTERESIS)))) {
423
return [self RB___collapse];
425
newdim = minDimension;
427
} else if (newdim>olddim) {
430
if (newdim>(minDimension*(0.5+HYSTERESIS))) {
431
newdim = MAX(newdim,[self RB___expandAndSetToMinimum:YES]);
436
if (newdim>maxDimension) {
437
newdim = maxDimension;
440
if ((int)newdim!=(int)olddim) {
441
// The dimension has changed.
442
increment = newdim-olddim;
443
DIM(frame.size) = newdim;
445
DIM(frame.origin) -= increment;
447
// We call super instead of self here to postpone adjusting subviews for nested splitviews.
448
// [super setFrameSize:frame.size];
449
[super setFrame:frame];
450
[sv RB___setMustClearFractions];
453
return newdim-olddim;
456
// This convenience method returns the number of subviews (surprise!)
457
- (NSUInteger)numberOfSubviews {
458
return [[self subviews] count];
461
// We return the deepest subview that's hit by aPoint. We also check with the delegate if aPoint is
462
// within an alternate drag view.
463
- (NSView*)hitTest:(NSPoint)aPoint {
464
if ([self mouse:aPoint inRect:[self frame]]) {
465
RBSplitView* sv = [self splitView];
466
id delegate = [sv delegate];
467
if ([delegate respondsToSelector:@selector(splitView:dividerForPoint:inSubview:)]) {
468
actDivider = [delegate splitView:sv dividerForPoint:aPoint inSubview:self];
469
if ((actDivider+1)<[sv RB___numberOfSubviews]) {
473
actDivider = NSNotFound;
474
NSView* result = [super hitTest:aPoint];
475
canDragWindow = ![[result opaqueAncestor] isDescendantOf:[[result window] contentView]];
481
// This method handles clicking and dragging in an empty portion of the subview, or in an alternate
482
// drag view as designated by the delegate.
483
- (void)mouseDown:(NSEvent*)theEvent {
484
NSWindow* window = [self window];
485
NSPoint where = [theEvent locationInWindow];
486
if (actDivider<NSNotFound) {
487
// Cache divider# here for the loop
488
NSUInteger thediv = actDivider;
489
// The mouse down was inside an alternate drag view; actDivider was just set in hitTest.
490
RBSplitView* sv = [self splitView];
491
NSPoint point = [sv convertPoint:where fromView:nil];
492
[[RBSplitView cursor:RBSVDragCursor] push];
493
NSPoint base = NSZeroPoint;
494
// Record the current divider coordinate.
495
CGFloat divc = [sv RB___dividerOrigin:thediv];
496
BOOL ishor = [sv isHorizontal];
497
[sv RB___setDragging:YES];
498
// Loop while the button is down.
499
while ((theEvent = [NSApp nextEventMatchingMask:NSLeftMouseDownMask|NSLeftMouseDraggedMask|NSLeftMouseUpMask untilDate:[NSDate distantFuture] inMode:NSEventTrackingRunLoopMode dequeue:YES])&&([theEvent type]!=NSLeftMouseUp)) {
500
// Set up a local autorelease pool for the loop to prevent buildup of temporary objects.
501
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
502
NSDisableScreenUpdates();
503
// This does the actual movement.
504
[sv RB___trackMouseEvent:theEvent from:point withBase:base inDivider:thediv];
505
if ([sv mustAdjust]) {
506
// If something changed, we clear fractions and redisplay.
507
[sv RB___setMustClearFractions];
510
// Change the drag point by the actual amount moved.
511
CGFloat newc = [sv RB___dividerOrigin:thediv];
512
DIM(point) += newc-divc;
514
NSEnableScreenUpdates();
517
[sv RB___setDragging:NO];
519
actDivider = NSNotFound;
522
if (canDragWindow&&[window isMovableByWindowBackground]&&![[self couplingSplitView] background]) {
523
// If we get here, it's a textured (metal) window, the mouse has gone down on an non-opaque portion
524
// of the subview, and our RBSplitView has a transparent background. RBSplitView returns NO to
525
// mouseDownCanMoveWindow, but the window should move here - after all, the window background
526
// is visible right here! So we fake it and move the window as intended. Mwahahaha!
527
where = [window convertBaseToScreen:where];
528
NSPoint origin = [window frame].origin;
529
// Now we loop handling mouse events until we get a mouse up event.
530
while ((theEvent = [NSApp nextEventMatchingMask:NSLeftMouseDownMask|NSLeftMouseDraggedMask|NSLeftMouseUpMask untilDate:[NSDate distantFuture] inMode:NSEventTrackingRunLoopMode dequeue:YES])&&([theEvent type]!=NSLeftMouseUp)) {
531
// Set up a local autorelease pool for the loop to prevent buildup of temporary objects.
532
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
533
NSPoint now = [window convertBaseToScreen:[theEvent locationInWindow]];
534
origin.x += now.x-where.x;
535
origin.y += now.y-where.y;
536
// Move the window by the mouse displacement since the last event.
537
[window setFrameOrigin:origin];
544
// These two methods encode and decode subviews.
545
- (void)encodeWithCoder:(NSCoder*)coder {
547
BOOL coll = [self isCollapsed];
549
// We can't encode a collapsed subview as-is, so we correct the frame size first and add WAYOUT
550
// to the origin to signal it was collapsed.
551
NSRect newf = frame = [self frame];
552
newf.origin.x += WAYOUT;
553
[super setFrameOrigin:newf.origin];
554
newf.size = savedSize;
555
[super setFrameSize:newf.size];
557
[super encodeWithCoder:coder];
559
[super setFrame:frame];
561
if ([coder allowsKeyedCoding]) {
562
[coder encodeObject:identifier forKey:@"identifier"];
563
[coder encodeInteger:tag forKey:@"tag"];
564
[coder encodeDouble:minDimension forKey:@"minDimension"];
565
[coder encodeDouble:maxDimension forKey:@"maxDimension"];
566
[coder encodeDouble:fraction forKey:@"fraction"];
567
[coder encodeBool:canCollapse forKey:@"canCollapse"];
569
[coder encodeObject:identifier];
570
[coder encodeValueOfObjCType:@encode(typeof(tag)) at:&tag];
571
[coder encodeValueOfObjCType:@encode(typeof(minDimension)) at:&minDimension];
572
[coder encodeValueOfObjCType:@encode(typeof(maxDimension)) at:&maxDimension];
573
[coder encodeValueOfObjCType:@encode(typeof(fraction)) at:&fraction];
574
[coder encodeValueOfObjCType:@encode(typeof(canCollapse)) at:&canCollapse];
578
- (id)initWithCoder:(NSCoder*)coder {
579
if ((self = [super initWithCoder:coder])) {
584
maxDimension = WAYOUT;
586
actDivider = NSNotFound;
588
previous = [self frame];
589
savedSize = previous.size;
590
if (previous.origin.x>=WAYOUT) {
591
// The subview was collapsed when encoded, so we correct the origin and collapse it.
592
BOOL ishor = [self splitViewIsHorizontal];
593
previous.origin.x -= WAYOUT;
594
DIM(previous.size) = 0.0;
595
[self setFrameOrigin:previous.origin];
596
[self setFrameSize:previous.size];
598
previous = NSZeroRect;
599
if ([coder allowsKeyedCoding]) {
600
[self setIdentifier:[coder decodeObjectForKey:@"identifier"]];
601
tag = [coder decodeIntegerForKey:@"tag"];
602
minDimension = [coder decodeDoubleForKey:@"minDimension"];
603
maxDimension = [coder decodeDoubleForKey:@"maxDimension"];
604
fraction = [coder decodeDoubleForKey:@"fraction"];
605
canCollapse = [coder decodeBoolForKey:@"canCollapse"];
607
[self setIdentifier:[coder decodeObject]];
608
[coder decodeValueOfObjCType:@encode(typeof(tag)) at:&tag];
609
[coder decodeValueOfObjCType:@encode(typeof(minDimension)) at:&minDimension];
610
[coder decodeValueOfObjCType:@encode(typeof(maxDimension)) at:&maxDimension];
611
[coder decodeValueOfObjCType:@encode(typeof(fraction)) at:&fraction];
612
[coder decodeValueOfObjCType:@encode(typeof(canCollapse)) at:&canCollapse];
620
@implementation RBSplitSubview (RB___SubviewAdditions)
622
// This hides/shows the subview without calling adjustSubviews.
623
- (void)RB___setHidden:(BOOL)flag {
624
[super setHidden:flag];
627
// This internal method returns the current animationData. It will always return nil if
628
// the receiver isn't the current owner and some other subview is already being animated.
629
// Otherwise, if the parameter is YES, a new animation will be started (or the current
630
// one will be restarted).
631
- (animationData*)RB___animationData:(BOOL)start resize:(BOOL)resize {
632
if (currentAnimation&&(currentAnimation->owner!=self)) {
633
// There already is an animation in progress on some other subview.
637
// We want to start (or restart) an animation.
638
RBSplitView* sv = [self splitView];
640
CGFloat dim = [self dimension];
641
// First assume the default time, then ask the delegate.
642
NSTimeInterval total = dim*(0.2/150.0);
643
id delegate = [sv delegate];
644
if ([delegate respondsToSelector:@selector(splitView:willAnimateSubview:withDimension:)]) {
645
total = [delegate splitView:sv willAnimateSubview:self withDimension:dim];
647
// No use animating anything shorter than the frametime.
648
if (total>FRAMETIME) {
649
if (!currentAnimation) {
650
currentAnimation = (animationData*)malloc(sizeof(animationData));
652
if (currentAnimation) {
653
currentAnimation->owner = self;
654
currentAnimation->stepsDone = 0;
655
currentAnimation->elapsedTime = 0.0;
656
currentAnimation->dimension = dim;
657
currentAnimation->collapsing = ![self isCollapsed];
658
currentAnimation->totalTime = total;
659
currentAnimation->finishTime = [NSDate timeIntervalSinceReferenceDate]+total;
660
currentAnimation->resizing = resize;
661
[sv RB___setDragging:YES];
663
} else if (currentAnimation) {
664
free(currentAnimation);
665
currentAnimation = NULL;
669
return currentAnimation;
672
// This internal method steps the animation to the next frame.
673
- (void)RB___stepAnimation {
674
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
675
animationData* anim = [self RB___animationData:NO resize:NO];
677
RBSplitView* sv = [self splitView];
678
NSTimeInterval remain = anim->finishTime-now;
679
NSRect frame = [self frame];
680
BOOL ishor = [sv isHorizontal];
681
// Continuing animation only makes sense if we still have at least FRAMETIME available.
682
if (remain>=FRAMETIME) {
683
CGFloat avg = anim->elapsedTime;
684
// We try to keep a record of how long it takes, on the average, to resize and adjust
685
// one animation frame.
686
if (anim->stepsDone) {
687
avg /= anim->stepsDone;
689
NSTimeInterval delay = MIN(0.0,FRAMETIME-avg);
690
// We adjust the new dimension proportionally to how much of the designated time has passed.
691
CGFloat dim = floor(anim->dimension*(remain-avg)/anim->totalTime);
693
if (!anim->collapsing) {
694
dim = anim->dimension-dim;
696
DIM(frame.size) = dim;
697
[self RB___setFrame:frame withFraction:0.0 notify:NO];
700
anim->elapsedTime += [NSDate timeIntervalSinceReferenceDate]-now;
702
// Schedule a timer to do the next animation step.
703
[self performSelector:@selector(RB___stepAnimation) withObject:nil afterDelay:delay inModes:[NSArray arrayWithObjects:NSDefaultRunLoopMode,NSModalPanelRunLoopMode,
704
NSEventTrackingRunLoopMode,nil]];
708
// We're finished, either collapse or expand entirely now.
709
if (anim->collapsing) {
710
DIM(frame.size) = 0.0;
711
[self RB___finishCollapse:frame withFraction:anim->dimension/[sv RB___dimensionWithoutDividers]];
713
CGFloat savemin,savemax;
714
CGFloat dim = [self RB___setMinAndMaxTo:anim->dimension savingMin:&savemin andMax:&savemax];
715
DIM(frame.size) = dim;
716
[self RB___finishExpand:frame withFraction:0.0];
717
minDimension = savemin;
718
maxDimension = savemax;
723
// This internal method stops the animation, if the receiver is being animated. It will
724
// return YES if the animation was stopped.
725
- (BOOL)RB___stopAnimation {
726
if (currentAnimation&&(currentAnimation->owner==self)) {
727
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(RB___stepAnimation) object:nil];
728
free(currentAnimation);
729
currentAnimation = NULL;
730
[[self splitView] RB___setDragging:NO];
736
// This internal method returns the actual visible dimension of the subview. Differs from -dimension in
737
// that it returns 0.0 if the subview is collapsed.
738
- (CGFloat)RB___visibleDimension {
739
BOOL ishor = [self splitViewIsHorizontal];
740
NSRect frame = [self frame];
741
return MAX(0.0,DIM(frame.size));
744
// This pair of internal methods is used only inside -[RBSplitView adjustSubviews] to copy subview data
745
// from and to that method's internal cache.
746
- (void)RB___copyIntoCache:(subviewCache*)cache {
748
cache->rect = [self frame];
749
cache->size = [self RB___visibleDimension];
750
cache->fraction = fraction;
751
cache->constrain = NO;
754
- (void)RB___updateFromCache:(subviewCache*)cache withTotalDimension:(CGFloat)value {
755
CGFloat dim = [self RB___visibleDimension];
756
if (cache->size>=1.0) {
757
// New state is not collapsed.
759
// Old state was not collapsed, so we just change the frame.
760
[self RB___setFrame:cache->rect withFraction:cache->fraction notify:YES];
762
// Old state was collapsed, so we expand it.
763
[self RB___finishExpand:cache->rect withFraction:cache->fraction];
766
// New state is collapsed.
768
// Old state was not collapsed, so we clear first responder and change the frame.
769
[self RB___clearResponder];
770
[self RB___finishCollapse:cache->rect withFraction:dim/value];
772
// It was collapsed already, but the frame may have changed, so we set it.
773
[self RB___setFrame:cache->rect withFraction:cache->fraction notify:YES];
778
// This internal method sets minimum and maximum values to the same value, saves the old values,
779
// and returns the new value (which will be limited to the old values).
780
- (CGFloat)RB___setMinAndMaxTo:(CGFloat)value savingMin:(CGFloat*)oldmin andMax:(CGFloat*)oldmax {
781
*oldmin = [self minDimension];
782
*oldmax = [self maxDimension];
789
minDimension = maxDimension = value;
793
// This internal method tries to clear the first responder, if the current responder is a descendant of
794
// the receiving subview. If so, it will set first responder to nil, redisplay the former responder and
795
// return YES. Returns NO otherwise.
796
- (BOOL)RB___clearResponder {
797
NSWindow* window = [self window];
799
NSView* responder = (NSView*)[window firstResponder];
800
if (responder&&[responder respondsToSelector:@selector(isDescendantOf:)]) {
801
if ([responder isDescendantOf:self]) {
802
if ([window makeFirstResponder:nil]) {
812
// This internal method collapses a subview.
813
// It returns the negative of the size of the subview before collapsing, or 0.0 if it wasn't collapsed.
814
- (CGFloat)RB___collapse {
815
CGFloat result = 0.0;
816
if (![self isCollapsed]) {
817
RBSplitView* sv = [self splitView];
818
if (sv&&[self canCollapse]) {
819
[self RB___clearResponder];
820
NSRect frame = [self frame];
821
BOOL ishor = [sv isHorizontal];
822
result = DIM(frame.size);
823
// For collapsed views, fraction will contain the fraction of the dimension previously occupied
824
DIM(frame.size) = 0.0;
825
[self RB___finishCollapse:frame withFraction:result/[sv RB___dimensionWithoutDividers]];
831
// This internal method finishes the collapse of a subview, stopping the animation if
832
// there is one, and calling the delegate method if there is one.
833
- (void)RB___finishCollapse:(NSRect)rect withFraction:(double)value {
834
RBSplitView* sv = [self splitView];
835
BOOL finish = [self RB___stopAnimation];
836
[self RB___setFrame:rect withFraction:value notify:YES];
837
[sv RB___setMustClearFractions];
841
id delegate = [sv delegate];
842
if ([delegate respondsToSelector:@selector(splitView:didCollapse:)]) {
843
[delegate splitView:sv didCollapse:self];
847
// This internal method expands a subview. setToMinimum will usually be YES during a divider drag.
848
// It returns the size of the subview after expanding, or 0.0 if it wasn't expanded.
849
- (CGFloat)RB___expandAndSetToMinimum:(BOOL)setToMinimum {
850
CGFloat result = 0.0;
851
RBSplitView* sv = [self splitView];
852
if (sv&&[self isCollapsed]) {
853
NSRect frame = [super frame];
854
double frac = fraction;
855
BOOL ishor = [sv isHorizontal];
857
result = DIM(frame.size) = minDimension;
859
result = [sv RB___dimensionWithoutDividers]*frac;
860
// We need to apply a compensation factor for proportional resizing in adjustSubviews.
861
CGFloat newdim = floor((frac>=1.0)?result:result/(1.0-frac));
862
DIM(frame.size) = newdim;
863
result = floor(result);
865
[self RB___finishExpand:frame withFraction:0.0];
870
// This internal method finishes the the expansion of a subview, stopping the animation if
871
// there is one, and calling the delegate method if there is one.
872
- (void)RB___finishExpand:(NSRect)rect withFraction:(double)value {
873
RBSplitView* sv = [self splitView];
874
BOOL finish = [self RB___stopAnimation];
875
[self RB___setFrame:rect withFraction:value notify:YES];
876
[sv RB___setMustClearFractions];
880
id delegate = [sv delegate];
881
if ([delegate respondsToSelector:@selector(splitView:didExpand:)]) {
882
[delegate splitView:sv didExpand:self];
886
// These internal methods set the subview's frame or size, and also store a fraction value
887
// which is used to ensure repeatability when the whole split view is resized.
888
- (void)RB___setFrame:(NSRect)rect withFraction:(double)value notify:(BOOL)notify {
889
RBSplitView* sv = [self splitView];
892
delegate = [sv delegate];
893
// If the delegate method isn't implemented, we ignore the delegate altogether.
894
if ([delegate respondsToSelector:@selector(splitView:changedFrameOfSubview:from:to:)]) {
895
// If the rects are equal, the delegate isn't called.
896
if (NSEqualRects(previous,rect)) {
904
[self setFrame:rect];
906
[delegate splitView:sv changedFrameOfSubview:self from:previous to:rect];
907
previous = delegate?rect:NSZeroRect;
910
- (void)RB___setFrameSize:(NSSize)size withFraction:(double)value {
911
[[self splitView] setMustAdjust];
912
[self setFrameSize:size];
916
// This internal method gets the fraction value.
917
- (double)RB___fraction {