2
// RBSplitSubview.m version 1.1.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.
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
// 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];
87
[sv adjustSubviewsExcepting:self];
92
// RBSplitSubviews can't be in the responder chain.
93
- (BOOL)acceptsFirstResponder {
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];
104
sv = [self couplingSplitView];
106
return [sv background]==nil;
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;
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;
130
// This returns the outermost directly containing RBSplitView, or nil.
131
- (RBSplitView*)outermostSplitView {
134
while ((sv = [sv superview])&&[sv isKindOfClass:[RBSplitView class]]) {
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:(OOInteger)theTag {
155
- (void)setIdentifier:(NSString*)aString {
156
[identifier autorelease];
157
identifier = aString?[aString retain]:@"";
160
- (NSString*)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];
181
[self removeFromSuperviewWithoutNeedingDisplay];
182
NSArray* subviews = [sv subviews];
183
if (newPosition>=[subviews count]) {
184
[sv addSubview:self positioned:NSWindowAbove relativeTo:nil];
186
[sv addSubview:self positioned:NSWindowBelow relativeTo:[subviews objectAtIndex:newPosition]];
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.
199
return [self RB___visibleDimension]>([self canCollapse]?0.0:minDimension);
202
// Tests whether the subview can expand further.
204
return [self RB___visibleDimension]<maxDimension;
207
// Returns the subview's status.
208
- (RBSSubviewStatus)status {
209
animationData* anim = [self RB___animationData:NO resize:NO];
211
return anim->collapsing?RBSSubviewCollapsing:RBSSubviewExpanding;
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) {
224
id delegate = [sv delegate];
225
if ([delegate respondsToSelector:@selector(splitView:canCollapse:)]) {
226
result = [delegate splitView:sv canCollapse:self];
231
// This sets the subview's "canCollapse" flag. Ignored if the delegate's splitView:canCollapse:
232
// method is implemented.
233
- (void)setCanCollapse:(BOOL)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.
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.
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];
278
[self RB___collapse];
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];
291
[self RB___expandAndSetToMinimum: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 {
306
- (float)maxDimension {
310
- (void)setMinDimension:(float)newMinDimension andMaxDimension:(float)newMaxDimension {
311
minDimension = MAX(1.0,floorf(newMinDimension));
312
if (newMaxDimension<1.0) {
313
newMaxDimension = WAYOUT;
315
maxDimension = MAX(minDimension,floorf(newMaxDimension));
316
float dim = [self dimension];
317
if ((dim<minDimension)||(dim>maxDimension)) {
318
[[self splitView] setMustAdjust];
322
// This returns the subview's dimension. If it's collapsed, it returns the dimension it would have
325
float dim = [self RB___visibleDimension];
327
dim = [[self splitView] RB___dimensionWithoutDividers]*fraction;
328
if (dim<minDimension) {
330
} else if (dim>maxDimension) {
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];
344
// We're not collapsed, set the size and adjust other subviews.
346
[self setFrameSize:size];
347
[sv adjustSubviewsExcepting:self];
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];
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];
362
NSRectFillUsingOperation(rect,NSCompositeSourceOver);
364
id del = [sv delegate];
365
if ([del respondsToSelector:@selector(splitView:willDrawSubview:inRect:)]) {
366
[del splitView:sv willDrawSubview:self inRect:rect];
370
// We check if the RBSplitView must be adjusted before redisplaying programmatically.
371
// if so, we adjust and display the whole RBSplitView.
373
RBSplitView* sv = [self splitView];
375
if ([sv mustAdjust]) {
383
// RBSplitSubviews will always resize their own subviews.
384
- (BOOL)autoresizesSubviews {
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];
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])) {
400
// The subviews can be resized, so we restore the saved size.
401
oldBoundsSize = savedSize;
403
// We save the size every time the subview's subviews are resized within the limits.
405
savedSize = frame.size;
406
[super resizeSubviewsWithOldSize:oldBoundsSize];
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)) {
421
BOOL ishor = [sv isHorizontal];
422
NSRect frame = [self frame];
423
float olddim = DIM(frame.size);
424
float newdim = MAX(0.0,olddim+increment);
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];
431
newdim = minDimension;
433
} else if (newdim>olddim) {
436
if (newdim>(minDimension*(0.5+HYSTERESIS))) {
437
newdim = MAX(newdim,[self RB___expandAndSetToMinimum:YES]);
442
if (newdim>maxDimension) {
443
newdim = maxDimension;
446
if ((int)newdim!=(int)olddim) {
447
// The dimension has changed.
448
increment = newdim-olddim;
449
DIM(frame.size) = newdim;
451
DIM(frame.origin) -= increment;
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];
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)) {
479
actDivider = NSNotFound;
480
NSView* result = [super hitTest:aPoint];
481
canDragWindow = ![result isOpaque];
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];
514
// Change the drag point by the actual amount moved.
515
float newc = [sv RB___dividerOrigin:actDivider];
516
DIM(point) += newc-divc;
518
NSEnableScreenUpdates();
521
[sv RB___setDragging:NO];
523
actDivider = NSNotFound;
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];
548
// These two methods encode and decode subviews.
549
- (void)encodeWithCoder:(NSCoder*)coder {
551
BOOL coll = [self isCollapsed];
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];
561
[super encodeWithCoder:coder];
563
[super setFrame:frame];
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"];
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];
582
- (id)initWithCoder:(NSCoder*)coder {
583
if ((self = [super initWithCoder:coder])) {
588
maxDimension = WAYOUT;
590
actDivider = NSNotFound;
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];
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"];
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];
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.
641
// We want to start (or restart) an animation.
642
RBSplitView* sv = [self splitView];
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];
651
// No use animating anything shorter than the frametime.
652
if (total>FRAMETIME) {
653
if (!currentAnimation) {
654
currentAnimation = (animationData*)malloc(sizeof(animationData));
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];
667
} else if (currentAnimation) {
668
free(currentAnimation);
669
currentAnimation = NULL;
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];
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 avg = anim->elapsedTime;
688
// We try to keep a record of how long it takes, on the average, to resize and adjust
689
// one animation frame.
690
if (anim->stepsDone) {
691
avg /= anim->stepsDone;
693
NSTimeInterval delay = MIN(0.0,FRAMETIME-avg);
694
// We adjust the new dimension proportionally to how much of the designated time has passed.
695
float dim = floorf(anim->dimension*(remain-avg)/anim->totalTime);
697
if (!anim->collapsing) {
698
dim = anim->dimension-dim;
700
DIM(frame.size) = dim;
701
[self RB___setFrame:frame withFraction:0.0 notify:NO];
704
anim->elapsedTime += [NSDate timeIntervalSinceReferenceDate]-now;
706
// Schedule a timer to do the next animation step.
707
[self performSelector:@selector(RB___stepAnimation) withObject:nil afterDelay:delay inModes:[NSArray arrayWithObjects:NSDefaultRunLoopMode,NSModalPanelRunLoopMode,
708
NSEventTrackingRunLoopMode,nil]];
712
// We're finished, either collapse or expand entirely now.
713
if (anim->collapsing) {
714
DIM(frame.size) = 0.0;
715
[self RB___finishCollapse:frame withFraction:anim->dimension/[sv RB___dimensionWithoutDividers]];
717
float savemin,savemax;
718
float dim = [self RB___setMinAndMaxTo:anim->dimension savingMin:&savemin andMax:&savemax];
719
DIM(frame.size) = dim;
720
[self RB___finishExpand:frame withFraction:0.0];
721
minDimension = savemin;
722
maxDimension = savemax;
727
// This internal method stops the animation, if the receiver is being animated. It will
728
// return YES if the animation was stopped.
729
- (BOOL)RB___stopAnimation {
730
if (currentAnimation&&(currentAnimation->owner==self)) {
731
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(RB___stepAnimation) object:nil];
732
free(currentAnimation);
733
currentAnimation = NULL;
734
[[self splitView] RB___setDragging:NO];
740
// This internal method returns the actual visible dimension of the subview. Differs from -dimension in
741
// that it returns 0.0 if the subview is collapsed.
742
- (float)RB___visibleDimension {
743
BOOL ishor = [self splitViewIsHorizontal];
744
NSRect frame = [self frame];
745
return MAX(0.0,DIM(frame.size));
748
// This pair of internal methods is used only inside -[RBSplitView adjustSubviews] to copy subview data
749
// from and to that method's internal cache.
750
- (void)RB___copyIntoCache:(subviewCache*)cache {
752
cache->rect = [self frame];
753
cache->size = [self RB___visibleDimension];
754
cache->fraction = fraction;
755
cache->constrain = NO;
758
- (void)RB___updateFromCache:(subviewCache*)cache withTotalDimension:(float)value {
759
float dim = [self RB___visibleDimension];
760
if (cache->size>=1.0) {
761
// New state is not collapsed.
763
// Old state was not collapsed, so we just change the frame.
764
[self RB___setFrame:cache->rect withFraction:cache->fraction notify:YES];
766
// Old state was collapsed, so we expand it.
767
[self RB___finishExpand:cache->rect withFraction:cache->fraction];
770
// New state is collapsed.
772
// Old state was not collapsed, so we clear first responder and change the frame.
773
[self RB___clearResponder];
774
[self RB___finishCollapse:cache->rect withFraction:dim/value];
776
// It was collapsed already, but the frame may have changed, so we set it.
777
[self RB___setFrame:cache->rect withFraction:cache->fraction notify:YES];
782
// This internal method sets minimum and maximum values to the same value, saves the old values,
783
// and returns the new value (which will be limited to the old values).
784
- (float)RB___setMinAndMaxTo:(float)value savingMin:(float*)oldmin andMax:(float*)oldmax {
785
*oldmin = [self minDimension];
786
*oldmax = [self maxDimension];
793
minDimension = maxDimension = value;
797
// This internal method tries to clear the first responder, if the current responder is a descendant of
798
// the receiving subview. If so, it will set first responder to nil, redisplay the former responder and
799
// return YES. Returns NO otherwise.
800
- (BOOL)RB___clearResponder {
801
NSWindow* window = [self window];
803
NSView* responder = (NSView*)[window firstResponder];
804
if (responder&&[responder respondsToSelector:@selector(isDescendantOf:)]) {
805
if ([responder isDescendantOf:self]) {
806
if ([window makeFirstResponder:nil]) {
816
// This internal method collapses a subview.
817
// It returns the negative of the size of the subview before collapsing, or 0.0 if it wasn't collapsed.
818
- (float)RB___collapse {
820
if (![self isCollapsed]) {
821
RBSplitView* sv = [self splitView];
822
if (sv&&[self canCollapse]) {
823
[self RB___clearResponder];
824
NSRect frame = [self frame];
825
BOOL ishor = [sv isHorizontal];
826
result = DIM(frame.size);
827
// For collapsed views, fraction will contain the fraction of the dimension previously occupied
828
DIM(frame.size) = 0.0;
829
[self RB___finishCollapse:frame withFraction:result/[sv RB___dimensionWithoutDividers]];
835
// This internal method finishes the collapse of a subview, stopping the animation if
836
// there is one, and calling the delegate method if there is one.
837
- (void)RB___finishCollapse:(NSRect)rect withFraction:(double)value {
838
RBSplitView* sv = [self splitView];
839
BOOL finish = [self RB___stopAnimation];
840
[self RB___setFrame:rect withFraction:value notify:YES];
841
[sv RB___setMustClearFractions];
845
id delegate = [sv delegate];
846
if ([delegate respondsToSelector:@selector(splitView:didCollapse:)]) {
847
[delegate splitView:sv didCollapse:self];
851
// This internal method expands a subview. setToMinimum will usually be YES during a divider drag.
852
// It returns the size of the subview after expanding, or 0.0 if it wasn't expanded.
853
- (float)RB___expandAndSetToMinimum:(BOOL)setToMinimum {
855
RBSplitView* sv = [self splitView];
856
if (sv&&[self isCollapsed]) {
857
NSRect frame = [super frame];
858
double frac = fraction;
859
BOOL ishor = [sv isHorizontal];
861
result = DIM(frame.size) = minDimension;
863
result = [sv RB___dimensionWithoutDividers]*frac;
864
// We need to apply a compensation factor for proportional resizing in adjustSubviews.
865
float newdim = floorf((frac>=1.0)?result:result/(1.0-frac));
866
DIM(frame.size) = newdim;
867
result = floorf(result);
869
[self RB___finishExpand:frame withFraction:0.0];
874
// This internal method finishes the the expansion of a subview, stopping the animation if
875
// there is one, and calling the delegate method if there is one.
876
- (void)RB___finishExpand:(NSRect)rect withFraction:(double)value {
877
RBSplitView* sv = [self splitView];
878
BOOL finish = [self RB___stopAnimation];
879
[self RB___setFrame:rect withFraction:value notify:YES];
880
[sv RB___setMustClearFractions];
884
id delegate = [sv delegate];
885
if ([delegate respondsToSelector:@selector(splitView:didExpand:)]) {
886
[delegate splitView:sv didExpand:self];
890
// These internal methods set the subview's frame or size, and also store a fraction value
891
// which is used to ensure repeatability when the whole split view is resized.
892
- (void)RB___setFrame:(NSRect)rect withFraction:(double)value notify:(BOOL)notify {
893
RBSplitView* sv = [self splitView];
896
delegate = [sv delegate];
897
// If the delegate method isn't implemented, we ignore the delegate altogether.
898
if ([delegate respondsToSelector:@selector(splitView:changedFrameOfSubview:from:to:)]) {
899
// If the rects are equal, the delegate isn't called.
900
if (NSEqualRects(previous,rect)) {
908
[self setFrame:rect];
910
[delegate splitView:sv changedFrameOfSubview:self from:previous to:rect];
911
previous = delegate?rect:NSZeroRect;
914
- (void)RB___setFrameSize:(NSSize)size withFraction:(double)value {
915
[[self splitView] setMustAdjust];
916
[self setFrameSize:size];
920
// This internal method gets the fraction value.
921
- (double)RB___fraction {