Oolite 1.91.0.7604-240417-a536cbe
Loading...
Searching...
No Matches
RBSplitSubview.m
Go to the documentation of this file.
1//
2// RBSplitSubview.m version 1.2
3// RBSplitView
4//
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.
8//
9
10#import "RBSplitView.h"
12
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.
17
18@implementation RBSplitSubview
19
20// This class method returns YES if an animation is in progress.
21+ (BOOL)animating {
22 return currentAnimation!=NULL;
23}
24
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;
35 identifier = @"";
36 previous = NSZeroRect;
37 savedSize = frame.size;
38 actDivider = NSNotFound;
39 canDragWindow = NO;
40 }
41 return self;
42}
43
44// Just releases our stuff when going away.
45- (void)dealloc {
46 [identifier release];
47 [super dealloc];
48}
49
50// These return nil since we're not a RBSplitView (they're overridden there).
52 return nil;
53}
54
56 return nil;
57}
58
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 {
62}
63
64- (BOOL)isCoupled {
65 return NO;
66}
67
68// RBSplitSubviews are never flipped, unless they're RBSplitViews.
69- (BOOL)isFlipped {
70 return NO;
71}
72
73// We copy the opacity of the owning split view.
74- (BOOL)isOpaque {
75 return [[self couplingSplitView] isOpaque];
76}
77
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];
83 if (flag) {
84 [sv adjustSubviews];
85 } else {
87 }
88 }
89}
90
91// RBSplitSubviews can't be in the responder chain.
93 return NO;
94}
95
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.
100 id result = [self superview];
101 if ([result isKindOfClass:[RBSplitView class]]) {
102 return (RBSplitView*)result;
103 }
104 return nil;
105}
106
107// This also returns the owning splitview. It's overridden for nested RBSplitViews.
109 id result = [self superview];
110 if ([result isKindOfClass:[RBSplitView class]]) {
111 return (RBSplitView*)result;
112 }
113 return nil;
114}
115
116// This returns the outermost directly containing RBSplitView, or nil.
118 id result = nil;
119 id sv = self;
120 while ((sv = [sv superview])&&[sv isKindOfClass:[RBSplitView class]]) {
121 result = sv;
122 }
123 return result;
124}
125
126// This convenience method returns YES if the containing RBSplitView is horizontal.
127- (BOOL)splitViewIsHorizontal {
128 return [[self splitView] isHorizontal];
129}
130
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 {
134 tag = theTag;
135}
136
137- (NSInteger)tag {
138 return tag;
139}
140
141- (void)setIdentifier:(NSString*)aString {
142 [identifier autorelease];
143 identifier = aString?[aString retain]:@"";
144}
145
146- (NSString*)identifier {
147 return identifier;
148}
149
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];
154}
155
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;
161}
162
163- (void)setPosition:(NSUInteger)newPosition {
164 RBSplitView* sv = [self splitView];
165 if (sv) {
166 [self retain];
167 [self removeFromSuperviewWithoutNeedingDisplay];
168 NSArray* subviews = [sv subviews];
169 if (newPosition>=[subviews count]) {
170 [sv addSubview:self positioned:NSWindowAbove relativeTo:nil];
171 } else {
172 [sv addSubview:self positioned:NSWindowBelow relativeTo:[subviews objectAtIndex:newPosition]];
173 }
174 [self release];
175 }
176}
177
178// Tests whether the subview is collapsed.
179- (BOOL)isCollapsed {
180 return [self RB___visibleDimension]<=0.0;
181}
182
183// Tests whether the subview can shrink further.
184- (BOOL)canShrink {
185 return [self RB___visibleDimension]>([self canCollapse]?0.0:minDimension);
186}
187
188// Tests whether the subview can expand further.
189- (BOOL)canExpand {
190 return [self RB___visibleDimension]<maxDimension;
191}
192
193// Returns the subview's status.
195 animationData* anim = [self RB___animationData:NO resize:NO];
196 if (anim) {
198 }
199 return [self RB___visibleDimension]<=0.0?RBSSubviewCollapsed:RBSSubviewNormal;
200}
201
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) {
208 return NO;
209 }
210 id delegate = [sv delegate];
211 if ([delegate respondsToSelector:@selector(splitView:canCollapse:)]) {
212 result = [delegate splitView:sv canCollapse:self];
213 }
214 return result;
215}
216
217// This sets the subview's "canCollapse" flag. Ignored if the delegate's splitView:canCollapse:
218// method is implemented.
219- (void)setCanCollapse:(BOOL)flag {
220 canCollapse = flag;
221}
222
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.
228- (CGFloat)expand {
229 return [self RB___expandAndSetToMinimum:NO];
230}
231
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];
239}
240
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];
245}
246
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];
251}
252
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];
262 return YES;
263 } else {
264 [self RB___collapse];
265 }
266 }
267 }
268 return NO;
269}
270
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];
275 return YES;
276 } else {
277 [self RB___expandAndSetToMinimum:NO];
278 }
279 }
280 return NO;
281}
282
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 {
288 return minDimension;
289}
290
291- (CGFloat)maxDimension {
292 return maxDimension;
293}
294
295- (void)setMinDimension:(CGFloat)newMinDimension andMaxDimension:(CGFloat)newMaxDimension {
296 minDimension = MAX(1.0,floor(newMinDimension));
297 if (newMaxDimension<1.0) {
298 newMaxDimension = WAYOUT;
299 }
300 maxDimension = MAX(minDimension,floor(newMaxDimension));
301 CGFloat dim = [self dimension];
302 if ((dim<minDimension)||(dim>maxDimension)) {
303 [[self splitView] setMustAdjust];
304 }
305}
306
307// This returns the subview's dimension. If it's collapsed, it returns the dimension it would have
308// after expanding.
309- (CGFloat)dimension {
310 CGFloat dim = [self RB___visibleDimension];
311 if (dim<=0.0) {
312 dim = [[self splitView] RB___dimensionWithoutDividers]*fraction;
313 if (dim<minDimension) {
314 dim = minDimension;
315 } else if (dim>maxDimension) {
316 dim = maxDimension;
317 }
318 }
319 return dim;
320}
321
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;
329 }
330 RBSplitView* sv = [self splitView];
331 NSSize size = [self frame].size;
332 BOOL ishor = [sv isHorizontal];
333 if (DIM(size)>0.0) {
334// We're not collapsed, set the size and adjust other subviews.
335 DIM(size) = value;
336 [self setFrameSize:size];
337 [sv adjustSubviewsExcepting:self];
338 } else {
339// We're collapsed, adjust the fraction so that we'll have the (approximately) correct
340// dimension after expanding.
342 }
343}
344
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];
350 if (bg) {
351 [bg set];
352 NSRectFillUsingOperation(rect,NSCompositeSourceOver);
353 }
354 id del = [sv delegate];
355 if ([del respondsToSelector:@selector(splitView:willDrawSubview:inRect:)]) {
356 [del splitView:sv willDrawSubview:self inRect:rect];
357 }
358}
359
360// We check if the RBSplitView must be adjusted before redisplaying programmatically.
361// if so, we adjust and display the whole RBSplitView.
362- (void)display {
363 RBSplitView* sv = [self splitView];
364 if (sv) {
365 if ([sv mustAdjust]) {
366 [sv display];
367 } else {
368 [super display];
369 }
370 }
371}
372
373// RBSplitSubviews will always resize their own subviews.
374- (BOOL)autoresizesSubviews {
375 return YES;
376}
377
378// ...and we don't want that to change under any circumstances.
379- (void)setAutoresizesSubviews:(BOOL)flag {
380}
381
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];
385 if (sv) {
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])) {
393 if (notInLimits) {
394// The subviews can be resized, so we restore the saved size.
395 oldBoundsSize = savedSize;
396 }
397// We save the size every time the subview's subviews are resized within the limits.
398 notInLimits = NO;
399 savedSize = frame.size;
400 [super resizeSubviewsWithOldSize:oldBoundsSize];
401 } else {
402 notInLimits = YES;
403 }
404 }
405}
406
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)) {
413 return 0.0;
414 }
415 BOOL ishor = [sv isHorizontal];
416 NSRect frame = [self frame];
417 CGFloat olddim = DIM(frame.size);
418 CGFloat newdim = MAX(0.0,olddim+increment);
419 if (newdim<olddim) {
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];
424 }
425 newdim = minDimension;
426 }
427 } else if (newdim>olddim) {
428 if (olddim<1.0) {
429// Expand if needed.
430 if (newdim>(minDimension*(0.5+HYSTERESIS))) {
431 newdim = MAX(newdim,[self RB___expandAndSetToMinimum:YES]);
432 } else {
433 return 0.0;
434 }
435 }
436 if (newdim>maxDimension) {
437 newdim = maxDimension;
438 }
439 }
440 if ((int)newdim!=(int)olddim) {
441// The dimension has changed.
442 increment = newdim-olddim;
443 DIM(frame.size) = newdim;
444 if (move) {
445 DIM(frame.origin) -= increment;
446 }
447// We call super instead of self here to postpone adjusting subviews for nested splitviews.
448// [super setFrameSize:frame.size];
449 [super setFrame:frame];
451 [sv setMustAdjust];
452 }
453 return newdim-olddim;
454}
455
456// This convenience method returns the number of subviews (surprise!)
457- (NSUInteger)numberOfSubviews {
458 return [[self subviews] count];
459}
460
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]) {
470 return self;
471 }
472 }
473 actDivider = NSNotFound;
474 NSView* result = [super hitTest:aPoint];
475 canDragWindow = ![[result opaqueAncestor] isDescendantOf:[[result window] contentView]];
476 return result;
477 }
478 return nil;
479}
480
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.
508 [sv display];
509 }
510// Change the drag point by the actual amount moved.
511 CGFloat newc = [sv RB___dividerOrigin:thediv];
512 DIM(point) += newc-divc;
513 divc = newc;
514 NSEnableScreenUpdates();
515 [pool drain];
516 }
517 [sv RB___setDragging:NO];
518 [NSCursor pop];
519 actDivider = NSNotFound;
520 return;
521 }
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];
538 where = now;
539 [pool drain];
540 }
541 }
542}
543
544// These two methods encode and decode subviews.
545- (void)encodeWithCoder:(NSCoder*)coder {
546 NSRect frame;
547 BOOL coll = [self isCollapsed];
548 if (coll) {
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];
556 }
557 [super encodeWithCoder:coder];
558 if (coll) {
559 [super setFrame:frame];
560 }
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"];
568 } else {
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];
575 }
576}
577
578- (id)initWithCoder:(NSCoder*)coder {
579 if ((self = [super initWithCoder:coder])) {
580 fraction = 0.0;
581 canCollapse = NO;
582 notInLimits = NO;
583 minDimension = 1.0;
585 identifier = @"";
586 actDivider = NSNotFound;
587 canDragWindow = NO;
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];
597 }
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"];
606 } else {
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];
613 }
614 }
615 return self;
616}
617
618@end
619
620@implementation RBSplitSubview (RB___SubviewAdditions)
621
622// This hides/shows the subview without calling adjustSubviews.
623- (void)RB___setHidden:(BOOL)flag {
624 [super setHidden:flag];
625}
626
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 {
633// There already is an animation in progress on some other subview.
634 return nil;
635 }
636 if (start) {
637// We want to start (or restart) an animation.
638 RBSplitView* sv = [self splitView];
639 if (sv) {
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];
646 }
647// No use animating anything shorter than the frametime.
648 if (total>FRAMETIME) {
649 if (!currentAnimation) {
650 currentAnimation = (animationData*)malloc(sizeof(animationData));
651 }
652 if (currentAnimation) {
653 currentAnimation->owner = self;
657 currentAnimation->collapsing = ![self isCollapsed];
659 currentAnimation->finishTime = [NSDate timeIntervalSinceReferenceDate]+total;
660 currentAnimation->resizing = resize;
661 [sv RB___setDragging:YES];
662 }
663 } else if (currentAnimation) {
664 free(currentAnimation);
665 currentAnimation = NULL;
666 }
667 }
668 }
669 return currentAnimation;
670}
671
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];
676 if (anim) {
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;
688 }
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);
692 if (dim>4.0) {
693 if (!anim->collapsing) {
694 dim = anim->dimension-dim;
695 }
696 DIM(frame.size) = dim;
697 [self RB___setFrame:frame withFraction:0.0 notify:NO];
698 [sv adjustSubviews];
699 [self display];
700 anim->elapsedTime += [NSDate timeIntervalSinceReferenceDate]-now;
701 ++anim->stepsDone;
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]];
705 return;
706 }
707 }
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]];
712 } else {
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;
719 }
720 }
721}
722
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 {
727 [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(RB___stepAnimation) object:nil];
728 free(currentAnimation);
729 currentAnimation = NULL;
730 [[self splitView] RB___setDragging:NO];
731 return YES;
732 }
733 return NO;
734}
735
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));
742}
743
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 {
747 cache->sub = self;
748 cache->rect = [self frame];
749 cache->size = [self RB___visibleDimension];
750 cache->fraction = fraction;
751 cache->constrain = NO;
752}
753
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.
758 if (dim>=1.0) {
759// Old state was not collapsed, so we just change the frame.
760 [self RB___setFrame:cache->rect withFraction:cache->fraction notify:YES];
761 } else {
762// Old state was collapsed, so we expand it.
763 [self RB___finishExpand:cache->rect withFraction:cache->fraction];
764 }
765 } else {
766// New state is collapsed.
767 if (dim>=1.0) {
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];
771 } else {
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];
774 }
775 }
776}
777
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];
783 if (value<*oldmin) {
784 value = *oldmin;
785 }
786 if (value>*oldmax) {
787 value = *oldmax;
788 }
789 minDimension = maxDimension = value;
790 return value;
791}
792
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];
798 if (window) {
799 NSView* responder = (NSView*)[window firstResponder];
800 if (responder&&[responder respondsToSelector:@selector(isDescendantOf:)]) {
801 if ([responder isDescendantOf:self]) {
802 if ([window makeFirstResponder:nil]) {
803 [responder display];
804 return YES;
805 }
806 }
807 }
808 }
809 return NO;
810}
811
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]];
826 }
827 }
828 return -result;
829}
830
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];
838 if (finish) {
839 [self display];
840 }
841 id delegate = [sv delegate];
842 if ([delegate respondsToSelector:@selector(splitView:didCollapse:)]) {
843 [delegate splitView:sv didCollapse:self];
844 }
845}
846
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];
856 if (setToMinimum) {
857 result = DIM(frame.size) = minDimension;
858 } else {
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);
864 }
865 [self RB___finishExpand:frame withFraction:0.0];
866 }
867 return result;
868}
869
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];
877 if (finish) {
878 [self display];
879 }
880 id delegate = [sv delegate];
881 if ([delegate respondsToSelector:@selector(splitView:didExpand:)]) {
882 [delegate splitView:sv didExpand:self];
883 }
884}
885
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];
890 id delegate = nil;
891 if (notify) {
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)) {
897 delegate = nil;
898 }
899 } else {
900 delegate = nil;
901 }
902 }
903 [sv setMustAdjust];
904 [self setFrame:rect];
905 fraction = value;
906 [delegate splitView:sv changedFrameOfSubview:self from:previous to:rect];
907 previous = delegate?rect:NSZeroRect;
908}
909
910- (void)RB___setFrameSize:(NSSize)size withFraction:(double)value {
911 [[self splitView] setMustAdjust];
912 [self setFrameSize:size];
913 fraction = value;
914}
915
916// This internal method gets the fraction value.
917- (double)RB___fraction {
918 return fraction;
919}
920
921@end
922
#define MAX(A, B)
Definition OOMaths.h:114
#define MIN(A, B)
Definition OOMaths.h:111
return self
unsigned count
return nil
float y
float x
RBSSubviewStatus
@ RBSSubviewCollapsing
@ RBSSubviewNormal
@ RBSSubviewExpanding
@ RBSSubviewCollapsed
static animationData * currentAnimation
#define FRAMETIME
#define OTHER(x)
#define DIM(x)
#define HYSTERESIS
#define WAYOUT
NSUInteger position()
RBSplitView * couplingSplitView()
NSUInteger actDivider
RBSplitView * coupledSplitView()
RBSSubviewStatus status()
NSString * description()
RBSplitView * asSplitView()
BOOL acceptsFirstResponder()
RBSplitView * outermostSplitView()
NSUInteger numberOfSubviews()
NSString * identifier
RBSplitView * splitView()
CGFloat RB___dimensionWithoutDividers()
IBOutlet id delegate
Definition RBSplitView.h:25
void adjustSubviews()
CGFloat RB___dividerOrigin:(NSUInteger indx)
NSColor * background
Definition RBSplitView.h:27
void adjustSubviewsExcepting:(RBSplitSubview *excepting)
void RB___setDragging:(BOOL flag)
void RB___trackMouseEvent:from:withBase:inDivider:(NSEvent *theEvent,[from] NSPoint where,[withBase] NSPoint base,[inDivider] NSUInteger indx)
NSCursor * cursor:(RBSVCursorType type)
Definition RBSplitView.m:33
void addSubview:positioned:relativeTo:(NSView *aView, [positioned] NSWindowOrderingMode place, [relativeTo] NSView *otherView)
void RB___setMustClearFractions()
void setMustAdjust()
BOOL isHorizontal
Definition RBSplitView.h:33
voidpf void uLong size
Definition ioapi.h:134
voidpf uLong int origin
Definition ioapi.h:140