Bug Summary

File:Frameworks/RBSplitView/Source/RBSplitSubview.m
Location:line 687, column 10
Description:dead initialization

Annotated Source Code

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