Bug Summary

File:Frameworks/RBSplitView/Source/RBSplitView.m
Location:line 670, column 12
Description:dead initialization

Annotated Source Code

1//
2// RBSplitView.m version 1.1.4
3// RBSplitView
4//
5// Created by Rainer Brockerhoff on 24/09/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// Please don't remove this copyright notice!
14static const unsigned char RBSplitView_Copyright[] __attribute__ ((used)) =
15 "RBSplitView 1.1.4 Copyright(c)2004-2006 by Rainer Brockerhoff <rainer@brockerhoff.net>.";
16
17// This vector keeps currently used cursors. nil means the default cursor.
18static NSCursor* cursors[RBSVCursorTypeCount] = {nil0};
19
20// Our own fMIN and fMAX
21static inline float fMIN(float a,float b) {
22 return a<b?a:b;
23}
24
25static inline float fMAX(float a,float b) {
26 return a>b?a:b;
27}
28
29@implementation RBSplitView
30
31// These class methods get and set the cursor used for each type.
32// Pass in nil to reset to the default cursor for that type.
33+ (NSCursor*)cursor:(RBSVCursorType)type {
34 if ((type>=0)&&(type<RBSVCursorTypeCount)) {
35 NSCursor* result = cursors[type];
36 if (result) {
37 return result;
38 }
39 switch (type) {
40 case RBSVHorizontalCursor:
41 return [NSCursor resizeUpDownCursor];
42 case RBSVVerticalCursor:
43 return [NSCursor resizeLeftRightCursor];
44 case RBSV2WayCursor:
45 return [NSCursor openHandCursor];
46 case RBSVDragCursor:
47 return [NSCursor closedHandCursor];
48 default:
49 break;
50 }
51 }
52 return [NSCursor currentCursor];
53}
54
55+ (void)setCursor:(RBSVCursorType)type toCursor:(NSCursor*)cursor {
56 if ((type>=0)&&(type<RBSVCursorTypeCount)) {
57 [cursors[type] release];
58 cursors[type] = [cursor retain];
59 }
60}
61
62// This class method clears the saved state(s) for a given autosave name from the defaults.
63+ (void)removeStateUsingName:(NSString*)name {
64 if ([name length]) {
65 NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
66 [defaults removeObjectForKey:[[self class] defaultsKeyForName:name isHorizontal:NO( BOOL ) 0]];
67 [defaults removeObjectForKey:[[self class] defaultsKeyForName:name isHorizontal:YES( BOOL ) 1]];
68 }
69}
70
71// This class method returns the actual key used to store autosave data in the defaults.
72+ (NSString*)defaultsKeyForName:(NSString*)name isHorizontal:(BOOL)orientation {
73 return [NSString stringWithFormat:@"RBSplitView %@ %@",orientation?@"H":@"V",name];
74}
75
76// This pair of methods gets and sets the autosave name, which allows restoring the subview's
77// state from the user defaults.
78// We take care not to allow nil autosaveNames.
79- (NSString*)autosaveName {
80 return autosaveName;
81}
82
83// Sets the autosaveName; this should be a unique key to be used to store the subviews' proportions
84// in the user defaults. Default is @"", which doesn't save anything. Set flag to YES to set
85// unique names for nested subviews. You are responsible for avoiding duplicates; avoid using
86// the characters '[' and ']' in autosaveNames.
87- (void)setAutosaveName:(NSString*)aString recursively:(BOOL)flag {
88 BOOL clear;
89 if ((clear = ![aString length])) {
90 aString = @"";
91 }
92 [RBSplitView removeStateUsingName:autosaveName];
93 [autosaveName autorelease];
94 autosaveName = [aString retain];
95 if (flag) {
96 NSArray* subviews = [self subviews];
97 int subcount = [subviews count];
98 int i;
99 for (i=0;i<subcount;i++) {
100 RBSplitView* sv = [[subviews objectAtIndex:i] asSplitView];
101 if (sv) {
102 NSString* subst = clear?@"":[aString stringByAppendingFormat:@"[%d]",i];
103 [sv setAutosaveName:subst recursively:YES( BOOL ) 1];
104 }
105 }
106 }
107}
108
109// Saves the current state of the subviews if there's a valid autosave name set. If the argument
110// is YES, it's then also called recursively for nested RBSplitViews. Returns YES if successful.
111// You must call restoreState explicity at least once before saveState will begin working.
112- (BOOL)saveState:(BOOL)recurse {
113// Saving the state is also disabled while dragging.
114 if (canSaveState&&![self isDragging]&&[autosaveName length]) {
115 [[NSUserDefaults standardUserDefaults] setObject:[self stringWithSavedState] forKey:[[self class] defaultsKeyForName:autosaveName isHorizontal:[self isHorizontal]]];
116 if (recurse) {
117 NSEnumerator* enumerator = [[self subviews] objectEnumerator];
118 RBSplitSubview* sub;
119 while ((sub = [enumerator nextObject])) {
120 [[sub asSplitView] saveState:YES( BOOL ) 1];
121 }
122 }
123 return YES( BOOL ) 1;
124 }
125 return NO( BOOL ) 0;
126}
127
128// Restores the saved state of the subviews if there's a valid autosave name set. If the argument
129// is YES, it's also called recursively for nested RBSplitViews. Returns YES if successful.
130// It's good policy to call adjustSubviews immediately after calling restoreState.
131- (BOOL)restoreState:(BOOL)recurse {
132 BOOL result = NO( BOOL ) 0;
133 if ([autosaveName length]) {
134 result = [self setStateFromString:[[NSUserDefaults standardUserDefaults] stringForKey:[[self class] defaultsKeyForName:autosaveName isHorizontal:[self isHorizontal]]]];
135 if (result&&recurse) {
136 NSEnumerator* enumerator = [[self subviews] objectEnumerator];
137 RBSplitSubview* sub;
138 while ((sub = [enumerator nextObject])) {
139 [[sub asSplitView] restoreState:YES( BOOL ) 1];
140 }
141 }
142 }
143 canSaveState = YES( BOOL ) 1;
144 return result;
145}
146
147// Returns an array with complete state information for the receiver and all subviews, taking
148// nesting into account. Don't store this array in a file, as its format might change in the
149// future; this is for taking a state snapshot and later restoring it with setStatesFromArray.
150- (NSArray*)arrayWithStates {
151 NSMutableArray* array = [NSMutableArray array];
152 [array addObject:[self stringWithSavedState]];
153 NSEnumerator* enumerator = [[self subviews] objectEnumerator];
154 RBSplitSubview* sub;
155 while ((sub = [enumerator nextObject])) {
156 RBSplitView* suv = [sub asSplitView];
157 if (suv) {
158 [array addObject:[suv arrayWithStates]];
159 } else {
160 [array addObject:[NSNull null]];
161 }
162 }
163 return array;
164}
165
166// Restores the state of the receiver and all subviews. The array must have been produced by a
167// previous call to arrayWithStates. Returns YES if successful. This will fail if you have
168// added or removed subviews in the meantime!
169// You need to call adjustSubviews after calling this.
170- (BOOL)setStatesFromArray:(NSArray*)array {
171 NSArray* subviews = [self subviews];
172 unsigned int count = [array count];
173 if (count==([subviews count]+1)) {
174 NSString* me = [array objectAtIndex:0];
175 if ([me isKindOfClass:[NSString class]]) {
176 if ([self setStateFromString:me]) {
177 unsigned int i;
178 for (i=1;i<count;i++) {
179 NSArray* item = [array objectAtIndex:i];
180 RBSplitView* suv = [[subviews objectAtIndex:i-1] asSplitView];
181 if ([item isKindOfClass:[NSArray class]]==(suv!=nil0)) {
182 if (suv&&![suv setStatesFromArray:item]) {
183 return NO( BOOL ) 0;
184 }
185 } else {
186 return NO( BOOL ) 0;
187 }
188 }
189 return YES( BOOL ) 1;
190 }
191 }
192 }
193 return NO( BOOL ) 0;
194}
195
196// Returns a string encoding the current state of all direct subviews. Does not check for nesting.
197// The string contains the number of direct subviews, then the dimension for each subview (which will
198// be negative for collapsed subviews), all separated by blanks.
199- (NSString*)stringWithSavedState {
200 NSArray* subviews = [self subviews];
201 NSMutableString* result = [NSMutableString stringWithFormat:@"%d",[subviews count]];
202 NSEnumerator* enumerator = [subviews objectEnumerator];
203 RBSplitSubview* sub;
204 while ((sub = [enumerator nextObject])) {
205 double size = [sub dimension];
206 if ([sub isCollapsed]) {
207 size = -size;
208 } else {
209 size += +[sub RB___fraction];
210 }
211 [result appendFormat:[sub isHidden]?@" %gH":@" %g",size];
212 }
213 return result;
214}
215
216// Readjusts all direct subviews according to the encoded string parameter.
217// The number of subviews must match. Returns YES if successful. Does not check for nesting.
218- (BOOL)setStateFromString:(NSString*)aString {
219 if ([aString length]) {
220 NSArray* parts = [aString componentsSeparatedByString:@" "];
221 NSArray* subviews = [self subviews];
222 int subcount = [subviews count];
223 int k = [parts count];
224 if ((k-->1)&&([[parts objectAtIndex:0] intValue]==subcount)&&(k==subcount)) {
225 int i;
226 NSRect frame = [self frame];
227 BOOL ishor = [self isHorizontal];
228 for (i=0;i<subcount;i++) {
229 NSString* part = [parts objectAtIndex:i+1];
230 BOOL hidden = [part hasSuffix:@"H"];
231 double size = [part doubleValue];
232 BOOL negative = size<=0.0;
233 if (negative) {
234 size = -size;
235 }
236 double fract = size;
237 size = floorf(size);
238 fract -= size;
239 DIM( ( ( float * ) & ( frame . size ) ) [ ishor ] )(frame.size) = size;
240 RBSplitSubview* sub = [subviews objectAtIndex:i];
241 [sub RB___setFrame:frame withFraction:fract notify:NO( BOOL ) 0];
242 if (negative) {
243 [sub RB___collapse];
244 }
245 [sub RB___setHidden:hidden];
246 }
247 [self setMustAdjust];
248 return YES( BOOL ) 1;
249 }
250 }
251 return NO( BOOL ) 0;
252}
253
254// This is the designated initializer for creating RBSplitViews programmatically. You can set the
255// divider image and other parameters afterwards.
256- (id)initWithFrame:(NSRect)frame {
257 self = [super initWithFrame:frame];
258 if (self) {
259 dividers = NULL( ( void * ) 0 );
260 isCoupled = YES( BOOL ) 1;
261 isDragging = NO( BOOL ) 0;
262 isInScrollView = NO( BOOL ) 0;
263 canSaveState = NO( BOOL ) 0;
264 [self setVertical:YES( BOOL ) 1];
265 [self setDivider:nil0];
266 [self setAutosaveName:nil0 recursively:NO( BOOL ) 0];
267 [self setBackground:nil0];
268 }
269 return self;
270}
271
272// This convenience initializer adds any number of subviews and adjusts them proportionally.
273- (id)initWithFrame:(NSRect)frame andSubviews:(unsigned)count {
274 self = [self initWithFrame:frame];
275 if (self) {
276 while (count-->0) {
277 [self addSubview:[[[RBSplitSubview alloc] initWithFrame:frame] autorelease]];
278 }
279 [self setMustAdjust];
280 }
281 return self;
282}
283
284// Frees retained objects when going away.
285- (void)dealloc {
286 if (dividers) {
287 free(dividers);
288 }
289 [autosaveName release];
290 [divider release];
291 [background release];
292 [super dealloc];
293}
294
295// Sets and gets the coupling between the view and its containing RBSplitView (if any). Coupled
296// RBSplitViews take some parameters, such as divider images, from the containing view. The default
297// is for nested RBSplitViews is YES; however, isCoupled returns NO if we're not nested.
298- (void)setCoupled:(BOOL)flag {
299 if (flag!=isCoupled) {
300 isCoupled = flag;
301// If we've just been uncoupled and there's no divider image, we copy it from the containing view.
302 if (!isCoupled&&!divider) {
303 [self setDivider:[[self splitView] divider]];
304 }
305 [self setMustAdjust];
306 }
307}
308
309- (BOOL)isCoupled {
310 return isCoupled&&([super splitView]!=nil0);
311}
312
313// This returns the containing splitview if they are coupled. It's guaranteed to return a RBSplitView or nil.
314- (RBSplitView*)couplingSplitView {
315 return isCoupled?[super couplingSplitView]:nil0;
316}
317
318// This returns self.
319- (RBSplitView*)asSplitView {
320 return self;
321}
322
323// This return self if we're really coupled to the owning splitview.
324- (RBSplitView*)coupledSplitView {
325 return [self isCoupled]?self:nil0;
326}
327
328// We always return NO, but do special handling in RBSplitSubview's mouseDown: method.
329- (BOOL)mouseDownCanMoveWindow {
330 return NO( BOOL ) 0;
331}
332
333// RBSplitViews must be flipped to work properly for horizontal dividers. As the subviews are never
334// flipped, this won't make your life harder.
335- (BOOL)isFlipped {
336 return YES( BOOL ) 1;
337}
338
339// Call this method to make sure that the subviews and divider rectangles are recalculated
340// properly before display.
341- (void)setMustAdjust {
342 mustAdjust = YES( BOOL ) 1;
343 [self setNeedsDisplay:YES( BOOL ) 1];
344}
345
346// Returns YES if there's a pending adjustment.
347- (BOOL)mustAdjust {
348 return mustAdjust;
349}
350
351// Returns YES if we're in a dragging loop.
352- (BOOL)isDragging {
353 return isDragging;
354}
355
356// Returns YES if the view is directly contained in an NSScrollView.
357- (BOOL)isInScrollView {
358 return isInScrollView;
359}
360
361// This pair of methods allows you to move the dividers for background windows while holding down
362// the command key, without bringing the window to the foreground.
363- (BOOL)acceptsFirstMouse:(NSEvent*)theEvent {
364 return ([theEvent modifierFlags]&NSCommandKeyMask)==0;
365}
366
367- (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent*)theEvent {
368 return ([theEvent modifierFlags]&NSCommandKeyMask)!=0;
369}
370
371// These 3 methods handle view background colors and opacity. The default is the window background.
372// Pass nil or a completely transparent color to setBackground to use transparency. If you set any
373// other background color, it will completely fill the RBSplitView (including subviews and dividers).
374// The view will be considered opaque only if its alpha is equal to 1.0.
375// For a nested, coupled RBSplitView, background and opacity are copied from the containing RBSplitView,
376// and setting the background has no effect.
377- (NSColor*)background {
378 RBSplitView* sv = [self couplingSplitView];
379 return sv?[sv background]:background;
380}
381
382- (void)setBackground:(NSColor*)color {
383 if (![self couplingSplitView]) {
384 [background autorelease];
385 background = color?([color alphaComponent]>0.0?[color retain]:nil0):nil0;
386 [self setNeedsDisplay:YES( BOOL ) 1];
387 }
388}
389
390- (BOOL)isOpaque {
391 RBSplitView* sv = [self couplingSplitView];
392 return sv?[sv isOpaque]:(background&&([background alphaComponent]>=1.0));
393}
394
395// This will make debugging a little easier by appending the state string to the
396// default description.
397- (NSString*)description {
398 return [NSString stringWithFormat:@"%@ {%@}",[super description],[self stringWithSavedState]];
399}
400
401// The following 3 methods handle divider orientation. The actual stored trait is horizontality,
402// but verticality is used for setting to conform to the NSSplitView convention.
403// For a nested RBSplitView, orientation is perpendicular to the containing RBSplitView, and
404// setting it has no effect. This parameter is not affected by coupling.
405// After changing the orientation you may want to restore the state with restoreState:.
406- (BOOL)isHorizontal {
407 RBSplitView* sv = [self splitView];
408 return sv?[sv isVertical]:isHorizontal;
409}
410
411- (BOOL)isVertical {
412 return 1-[self isHorizontal];
413}
414
415- (void)setVertical:(BOOL)flag {
416 if (![self splitView]&&(isHorizontal!=!flag)) {
417 BOOL ishor = isHorizontal = !flag;
418 NSSize size = divider?[divider size]:NSZeroSize;
419 [self setDividerThickness:DIM( ( ( float * ) & ( size ) ) [ ishor ] )(size)];
420 [self setMustAdjust];
421 }
422}
423
424// Returns the subview which a given identifier.
425- (RBSplitSubview*)subviewWithIdentifier:(NSString*)anIdentifier {
426 NSEnumerator* enumerator = [[self subviews] objectEnumerator];
427 RBSplitSubview* subview;
428 while ((subview = [enumerator nextObject])) {
429 if ([anIdentifier isEqualToString:[subview identifier]]) {
430 return subview;
431 }
432 }
433 return nil0;
434}
435
436// Returns the subview at a given position
437- (RBSplitSubview*)subviewAtPosition:(unsigned)position {
438 NSArray* subviews = [super subviews];
439 unsigned int subcount = [subviews count];
440 if (position<subcount) {
441 return [subviews objectAtIndex:position];
442 }
443 return nil0;
444}
445
446// This pair of methods gets and sets the delegate object. Delegates aren't retained.
447- (id)delegate {
448 return delegate;
449}
450
451- (void)setDelegate:(id)anObject {
452 delegate = anObject;
453}
454
455// This pair of methods gets and sets the divider image. Setting the image automatically adjusts the
456// divider thickness. A nil image means a 0-pixel wide divider, unless you set a thickness explicitly.
457// For a nested RBSplitView, the divider is copied from the containing RBSplitView, and
458// setting it has no effect. The returned image is always flipped.
459- (NSImage*)divider {
460 RBSplitView* sv = [self couplingSplitView];
461 return sv?[sv divider]:divider;
462}
463
464- (void)setDivider:(NSImage*)image {
465 if (![self couplingSplitView]) {
466 [divider autorelease];
467 if ([image isFlipped]) {
468// If the image is flipped, we just retain it.
469 divider = [image retain];
470 } else {
471// if the image isn't flipped, we copy the image instead of retaining it, and flip it.
472 divider = [image copy];
473 [divider setFlipped:YES( BOOL ) 1];
474 }
475// We set the thickness to 0.0 so the image dimension will prevail.
476 [self setDividerThickness:0.0];
477 [self setMustAdjust];
478 }
479}
480
481// This pair of methods gets and sets the divider thickness. It should be an integer value and at least
482// 0.0, so we make sure. Set it to 0.0 to make the image dimensions prevail.
483- (float)dividerThickness {
484 if (dividerThickness>0.0) {
485 return dividerThickness;
486 }
487 NSImage* divdr = [self divider];
488 if (divdr) {
489 NSSize size = [divdr size];
490 BOOL ishor = [self isHorizontal];
491 return DIM( ( ( float * ) & ( size ) ) [ ishor ] )(size);
492 }
493 return 0.0;
494}
495
496- (void)setDividerThickness:(float)thickness {
497 float t = fMAX(0.0,floorf(thickness));
498 if ((int)dividerThickness!=(int)t) {
499 dividerThickness = t;
500 [self setMustAdjust];
501 }
502}
503
504// These three methods add subviews. If aView isn't a RBSplitSubview, one is automatically inserted above
505// it, and aView's frame and resizing mask is set to occupy the entire RBSplitSubview.
506- (void)addSubview:(NSView*)aView {
507 if ([aView isKindOfClass:[RBSplitSubview class]]) {
508 [super addSubview:aView];
509 } else {
510 [aView setFrameOrigin:NSZeroPoint];
511 RBSplitSubview* sub = [[[RBSplitSubview alloc] initWithFrame:[aView frame]] autorelease];
512 [aView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
513 [sub addSubview:aView];
514 [super addSubview:sub];
515 }
516 [self setMustAdjust];
517}
518
519- (void)addSubview:(NSView*)aView positioned:(NSWindowOrderingMode)place relativeTo:(NSView*)otherView {
520 if ([aView isKindOfClass:[RBSplitSubview class]]) {
521 [super addSubview:aView positioned:place relativeTo:otherView];
522 } else {
523 [aView setFrameOrigin:NSZeroPoint];
524 RBSplitSubview* sub = [[[RBSplitSubview alloc] initWithFrame:[aView frame]] autorelease];
525 [aView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
526 [sub addSubview:aView];
527 [super addSubview:sub positioned:place relativeTo:otherView];
528 [aView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
529 }
530 [self setMustAdjust];
531}
532
533- (void)addSubview:(NSView*)aView atPosition:(unsigned)position {
534 RBSplitSubview* suv = [self subviewAtPosition:position];
535 if (suv) {
536 [self addSubview:aView positioned:NSWindowBelow relativeTo:suv];
537 } else {
538 [self addSubview:aView];
539 }
540}
541
542// This keeps the isInScrollView flag up-to-date.
543- (void)viewDidMoveToSuperview {
544 [super viewDidMoveToSuperview];
545 NSScrollView* scrollv = [self enclosingScrollView];
546 isInScrollView = scrollv?[scrollv documentView]==self:NO( BOOL ) 0;
547}
548
549// This makes sure the subviews are adjusted after a subview is removed.
550- (void)willRemoveSubview:(NSView*)subview {
551 if ([subview respondsToSelector:@selector(RB___stopAnimation)]) {
552 [(RBSplitSubview*)subview RB___stopAnimation];
553 }
554 [super willRemoveSubview:subview];
555 [self setMustAdjust];
556}
557
558// RBSplitViews never resize their subviews automatically.
559- (BOOL)autoresizesSubviews {
560 return NO( BOOL ) 0;
561}
562
563// This adjusts the subviews when the size is set. setFrame: calls this, so all is well. It calls
564// the delegate if implemented.
565- (void)setFrameSize:(NSSize)size {
566 NSSize oldsize = [self frame].size;
567 [super setFrameSize:size];
568 [self setMustAdjust];
569 if ([delegate respondsToSelector:@selector(splitView:wasResizedFrom:to:)]) {
570 BOOL ishor = [self isHorizontal];
571 float olddim = DIM( ( ( float * ) & ( oldsize ) ) [ ishor ] )(oldsize);
572 float newdim = DIM( ( ( float * ) & ( size ) ) [ ishor ] )(size);
573// The delegate is not called if the dimension hasn't changed.
574 if (((int)newdim!=(int)olddim)) {
575 [delegate splitView:self wasResizedFrom:olddim to:newdim];
576 }
577 }
578// We adjust the subviews only if the delegate didn't.
579 if (mustAdjust&&!isAdjusting) {
580 [self adjustSubviews];
581 }
582}
583
584// This method handles dragging and double-clicking dividers with the mouse. While dragging, the
585// "closed hand" cursor is shown. Double clicks are handled separately. Nothing will happen if
586// no divider image is set.
587- (void)mouseDown:(NSEvent*)theEvent {
588 if (!dividers) {
589 return;
590 }
591 NSArray* subviews = [self RB___subviews];
592 int subcount = [subviews count];
593 if (subcount<2) {
594 return;
595 }
596// If the mousedown was in an alternate dragview, or if there's no divider image, handle it in RBSplitSubview.
597 if ((actDivider<NSNotFound)||![self divider]) {
598 [super mouseDown:theEvent];
599 return;
600 }
601 NSPoint where = [self convertPoint:[theEvent locationInWindow] fromView:nil0];
602 BOOL ishor = [self isHorizontal];
603 int i;
604 --subcount;
605// Loop over the divider rectangles.
606 for (i=0;i<subcount;i++) {
607 NSRect* divdr = &dividers[i];
608 if ([self mouse:where inRect:*divdr]) {
609// leading points at the subview immediately leading the divider being tracked.
610 RBSplitView* leading = [subviews objectAtIndex:i];
611// trailing points at the subview immediately trailing the divider being tracked.
612 RBSplitView* trailing = [subviews objectAtIndex:i+1];
613 if ([delegate respondsToSelector:@selector(splitView:shouldHandleEvent:inDivider:betweenView:andView:)]) {
614 if (![delegate splitView:self shouldHandleEvent:theEvent inDivider:i betweenView:leading andView:trailing]) {
615 return;
616 }
617 }
618// If it's a double click, try to expand or collapse one of the neighboring subviews.
619 if ([theEvent clickCount]>1) {
620// If both are collapsed, we do nothing. If one of them is collapsed, we try to expand it.
621 if ([trailing isCollapsed]) {
622 if (![leading isCollapsed]) {
623 [self RB___tryToExpandTrailing:trailing leading:leading delta:-[trailing dimension]];
624 }
625 } else {
626 if ([leading isCollapsed]) {
627 [self RB___tryToExpandLeading:leading divider:i trailing:trailing delta:[leading dimension]];
628 } else {
629// If neither are collapsed, we check if both are collapsible.
630 BOOL lcan = [leading canCollapse];
631 BOOL tcan = [trailing canCollapse];
632 float ldim = [leading dimension];
633 if (lcan&&tcan) {
634// If both are collapsible, we try asking the delegate.
635 if ([delegate respondsToSelector:@selector(splitView:collapseLeading:orTrailing:)]) {
636 RBSplitSubview* sub = [delegate splitView:self collapseLeading:leading orTrailing:trailing];
637// If the delegate returns nil, neither view will collapse.
638 lcan = sub==leading;
639 tcan = sub==trailing;
640 } else {
641// Otherwise we try collapsing the smaller one. If they're equal, the trailing one will be collapsed.
642 lcan = ldim<[trailing dimension];
643 }
644 }
645// At this point, we'll try to collapse the leading subview.
646 if (lcan) {
647 [self RB___tryToShortenLeading:leading divider:i trailing:trailing delta:-ldim always:NO( BOOL ) 0];
648 }
649// If the leading subview didn't collapse for some reason, we try to collapse the trailing one.
650 if (!mustAdjust&&tcan) {
651 [self RB___tryToShortenTrailing:trailing divider:i leading:leading delta:[trailing dimension] always:NO( BOOL ) 0];
652 }
653 }
654 }
655// If the subviews have changed, clear the fractions, adjust and redisplay
656 if (mustAdjust) {
657 [self RB___setMustClearFractions];
658 RBSplitView* sv = [self splitView];
659 [sv?sv:self adjustSubviews];
660 [super display];
661 }
662 } else {
663// Single click; record the offsets within the divider rectangle and check for nesting.
664 float divt = [self dividerThickness];
665 float offset = DIM( ( ( float * ) & ( where ) ) [ ishor ] )(where)-DIM( ( ( float * ) & ( divdr -> origin ) ) [ ishor ] )(divdr->origin);
666// Check if the leading subview is nested and if yes, if one of its two-axis thumbs was hit.
667 int ldivdr = NSNotFound;
668 float loffset = 0.0;
669 NSPoint lwhere = where;
Value stored to 'lrect' during its initialization is never read
670 NSRect lrect = NSZeroRect;
671 if ((leading = [leading coupledSplitView])) {
672 ldivdr = [leading RB___dividerHitBy:lwhere relativeToView:self thickness:divt];
673 if (ldivdr!=NSNotFound) {
674 lrect = [leading RB___dividerRect:ldivdr relativeToView:self];
675 loffset = OTHER( ( ( float * ) & ( lwhere ) ) [ ! ishor ] )(lwhere)-OTHER( ( ( float * ) & ( lrect . origin ) ) [ ! ishor ] )(lrect.origin);
676 }
677 }
678// Check if the trailing subview is nested and if yes, if one of its two-axis thumbs was hit.
679 int tdivdr = NSNotFound;
680 float toffset = 0.0;
681 NSPoint twhere = where;
682 NSRect trect = NSZeroRect;
683 if ((trailing = [trailing coupledSplitView])) {
684 tdivdr = [trailing RB___dividerHitBy:twhere relativeToView:self thickness:divt];
685 if (tdivdr!=NSNotFound) {
686 trect = [trailing RB___dividerRect:tdivdr relativeToView:self];
687 toffset = OTHER( ( ( float * ) & ( twhere ) ) [ ! ishor ] )(twhere)-OTHER( ( ( float * ) & ( trect . origin ) ) [ ! ishor ] )(trect.origin);
688 }
689 }
690// Now we loop handling mouse events until we get a mouse up event, while showing the drag cursor.
691 [[RBSplitView cursor:RBSVDragCursor] push];
692 [self RB___setDragging:YES( BOOL ) 1];
693 while ((theEvent = [NSApp nextEventMatchingMask:NSLeftMouseDownMask|NSLeftMouseDraggedMask|NSLeftMouseUpMask untilDate:[NSDate distantFuture] inMode:NSEventTrackingRunLoopMode dequeue:YES( BOOL ) 1])&&([theEvent type]!=NSLeftMouseUp)) {
694// Set up a local autorelease pool for the loop to prevent buildup of temporary objects.
695 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
696 NSDisableScreenUpdates();
697// Track the mouse along the main coordinate.
698 [self RB___trackMouseEvent:theEvent from:where withBase:NSZeroPoint inDivider:i];
699 if (ldivdr!=NSNotFound) {
700// Track any two-axis thumbs for the leading nested RBSplitView.
701 [leading RB___trackMouseEvent:theEvent from:[self convertPoint:lwhere toView:leading] withBase:NSZeroPoint inDivider:ldivdr];
702 }
703 if (tdivdr!=NSNotFound) {
704// Track any two-axis thumbs for the trailing nested RBSplitView.
705 [trailing RB___trackMouseEvent:theEvent from:[self convertPoint:twhere toView:trailing] withBase:NSZeroPoint inDivider:tdivdr];
706 }
707 if (mustAdjust||[leading mustAdjust]||[trailing mustAdjust]) {
708// The mouse was dragged and the subviews changed, so we must redisplay, as
709// several divider rectangles may have changed.
710 RBSplitView* sv = [self splitView];
711 [sv?sv:self adjustSubviews];
712 [super display];
713 divdr = &dividers[i];
714// Adjust to the new cursor coordinates.
715 DIM( ( ( float * ) & ( where ) ) [ ishor ] )(where) = DIM( ( ( float * ) & ( divdr -> origin ) ) [ ishor ] )(divdr->origin)+offset;
716 if ((ldivdr!=NSNotFound)&&![leading isCollapsed]) {
717// Adjust for the leading nested RBSplitView's thumbs while it's not collapsed.
718 lrect = [leading RB___dividerRect:ldivdr relativeToView:self];
719 OTHER( ( ( float * ) & ( lwhere ) ) [ ! ishor ] )(lwhere) = OTHER( ( ( float * ) & ( lrect . origin ) ) [ ! ishor ] )(lrect.origin)+loffset;
720 }
721 if ((tdivdr!=NSNotFound)&&![trailing isCollapsed]) {
722// Adjust for the trailing nested RBSplitView's thumbs while it's not collapsed.
723 trect = [trailing RB___dividerRect:tdivdr relativeToView:self];
724 OTHER( ( ( float * ) & ( twhere ) ) [ ! ishor ] )(twhere) = OTHER( ( ( float * ) & ( trect . origin ) ) [ ! ishor ] )(trect.origin)+toffset;
725 }
726 }
727 NSEnableScreenUpdates();
728 [pool release];
729 }
730 [self RB___setDragging:NO( BOOL ) 0];
731// Redisplay the previous cursor.
732 [NSCursor pop];
733 }
734 }
735 }
736}
737
738// This will be called before the view will be redisplayed, so we adjust subviews if necessary.
739- (BOOL)needsDisplay {
740 if (mustAdjust&&!isAdjusting) {
741 [self adjustSubviews];
742 return YES( BOOL ) 1;
743 }
744 return [super needsDisplay];
745}
746
747// We implement awakeFromNib to restore the state. This works if an autosaveName is set in the nib.
748- (void)awakeFromNib {
749 if ([RBSplitSubview instancesRespondToSelector:@selector(awakeFromNib)]) {
750 [super awakeFromNib];
751 }
752 if (![self splitView]) {
753 [self restoreState:YES( BOOL ) 1];
754 }
755}
756
757// We check if subviews must be adjusted before redisplaying programmatically.
758- (void)display {
759 if (mustAdjust&&!isAdjusting) {
760 [self adjustSubviews];
761 }
762 [super display];
763}
764
765// This method draws the divider rectangles and then the two-axis thumbs if there are any.
766- (void)drawRect:(NSRect)rect {
767 [super drawRect:rect];
768 if (!dividers) {
769 return;
770 }
771 NSArray* subviews = [self RB___subviews];
772 int subcount = [subviews count];
773// Return if there are no dividers to draw.
774 if (subcount<2) {
775 return;
776 }
777 --subcount;
778 int i;
779// Cache the divider image.
780 NSImage* divdr = [self divider];
781 float divt = [self dividerThickness];
782// Loop over the divider rectangles.
783 for (i=0;i<subcount;i++) {
784// Check if we need to draw this particular divider.
785 if ([self needsToDrawRect:dividers[i]]) {
786 RBSplitView* leading = [subviews objectAtIndex:i];
787 RBSplitView* trailing = [subviews objectAtIndex:i+1];
788 BOOL lexp = divdr?![leading isCollapsed]:NO( BOOL ) 0;
789 BOOL texp = divdr?![trailing isCollapsed]:NO( BOOL ) 0;
790// We don't draw the divider image if either of the neighboring subviews is a non-collapsed
791// nested split view.
792 BOOL nodiv = (lexp&&[leading coupledSplitView])||(texp&&[trailing coupledSplitView]);
793 [self drawDivider:nodiv?nil0:divdr inRect:dividers[i] betweenView:leading andView:trailing];
794 if (divdr) {
795// Draw the corresponding two-axis thumbs if the leading view is a nested RBSplitView.
796 if ((leading = [leading coupledSplitView])&&lexp) {
797 [leading RB___drawDividersIn:self forDividerRect:dividers[i] thickness:divt];
798 }
799// Draw the corresponding two-axis thumbs if the trailing view is a nested RBSplitView.
800 if ((trailing = [trailing coupledSplitView])&&texp) {
801 [trailing RB___drawDividersIn:self forDividerRect:dividers[i] thickness:divt];
802 }
803 }
804 }
805 }
806}
807
808// This method draws dividers. You should never call it directly but you can override it when
809// subclassing, if you need custom dividers. It draws the divider image centered in the divider rectangle.
810// If we're drawing a two-axis thumb leading and trailing will be nil, and the rectangle
811// will be the thumb rectangle.
812// If there are nested split views this will be called once to draw the main divider rect,
813// and again for every thumb.
814- (void)drawDivider:(NSImage*)anImage inRect:(NSRect)rect betweenView:(RBSplitSubview*)leading andView:(RBSplitSubview*)trailing {
815// Fill the view with the background color (if there's any). Don't draw the background again for
816// thumbs.
817 if (leading||trailing) {
818 NSColor* bg = [self background];
819 if (bg) {
820 [bg set];
821 NSRectFillUsingOperation(rect,NSCompositeSourceOver);
822 }
823 }
824// Center the image, if there is one.
825 NSRect imrect = NSZeroRect;
826 NSRect dorect = NSZeroRect;
827 if (anImage) {
828 imrect.size = dorect.size = [anImage size];
829 dorect.origin = NSMakePoint(floorf(rect.origin.x+(rect.size.width-dorect.size.width)/2),
830 floorf(rect.origin.y+(rect.size.height-dorect.size.height)/2));
831 }
832// Ask the delegate for the final rect where the image should be drawn.
833 if ([delegate respondsToSelector:@selector(splitView:willDrawDividerInRect:betweenView:andView:withProposedRect:)]) {
834 dorect = [delegate splitView:self willDrawDividerInRect:rect betweenView:leading andView:trailing withProposedRect:dorect];
835 }
836// Draw the image if the delegate returned a non-empty rect.
837 if (!NSIsEmptyRect(dorect)) {
838 [anImage drawInRect:dorect fromRect:imrect operation:NSCompositeSourceOver fraction:1.0];
839 }
840}
841
842// This method should be called only from within the splitView:wasResizedFrom:to: delegate method
843// to keep some specific subview the same size.
844- (void)adjustSubviewsExcepting:(RBSplitSubview*)excepting {
845 [self RB___adjustSubviewsExcepting:[excepting isCollapsed]?nil0:excepting];
846}
847
848// This method adjusts subviews and divider rectangles.
849- (void)adjustSubviews {
850 [self RB___adjustSubviewsExcepting:nil0];
851}
852
853// This resets the appropriate cursors for each divider according to the orientation.
854// No cursors are shown if there is no divider image.
855- (void)resetCursorRects {
856 if (!dividers) {
857 return;
858 }
859 id del = [delegate respondsToSelector:@selector(splitView:cursorRect:forDivider:)]?delegate:nil0;
860 NSArray* subviews = [self RB___subviews];
861 int divcount = [subviews count]-1;
862 if ((divcount<1)||![self divider]) {
863 [del splitView:self cursorRect:NSZeroRect forDivider:0];
864 return;
865 }
866 int i;
867 NSCursor* cursor = [RBSplitView cursor:[self isVertical]?RBSVVerticalCursor:RBSVHorizontalCursor];
868 float divt = [self dividerThickness];
869 for (i=0;i<divcount;i++) {
870 RBSplitView* sub = [[subviews objectAtIndex:i] coupledSplitView];
871// If the leading subview is a nested RBSplitView, add the thumb rectangles first.
872 if (sub) {
873 [sub RB___addCursorRectsTo:self forDividerRect:dividers[i] thickness:divt];
874 }
875 sub = [[subviews objectAtIndex:i+1] coupledSplitView];
876// If the trailing subview is a nested RBSplitView, add the thumb rectangles first.
877 if (sub) {
878 [sub RB___addCursorRectsTo:self forDividerRect:dividers[i] thickness:divt];
879 }
880// Now add thedivider rectangle.
881 NSRect divrect = dividers[i];
882 if (del) {
883 divrect = [del splitView:self cursorRect:divrect forDivider:i];
884 }
885 if (!NSIsEmptyRect(divrect)) {
886 [self addCursorRect:divrect cursor:cursor];
887 }
888 }
889}
890
891// These two methods encode and decode RBSplitViews. One peculiarity is that we encode the divider image's
892// bitmap representation as data; this makes the nib files larger, but the user can just paste any image
893// into the RBSplitView inspector - or use the default divider image - without having to include it into the
894// project, too.
895- (void)encodeWithCoder:(NSCoder *)coder {
896 [super encodeWithCoder:coder];
897 if ([coder allowsKeyedCoding]) {
898 [coder encodeConditionalObject:delegate forKey:@"delegate"];
899 [coder encodeObject:autosaveName forKey:@"autosaveName"];
900 [coder encodeObject:[divider TIFFRepresentation] forKey:@"divider"];
901 [coder encodeObject:background forKey:@"background"];
902 [coder encodeFloat:dividerThickness forKey:@"dividerThickness"];
903 [coder encodeBool:isHorizontal forKey:@"isHorizontal"];
904 [coder encodeBool:isCoupled forKey:@"isCoupled"];
905 } else {
906 [coder encodeConditionalObject:delegate];
907 [coder encodeObject:autosaveName];
908 [coder encodeObject:[divider TIFFRepresentation]];
909 [coder encodeObject:background];
910 [coder encodeValueOfObjCType:@encode(typeof(dividerThickness)) at:&dividerThickness];
911 [coder encodeValueOfObjCType:@encode(typeof(isHorizontal)) at:&isHorizontal];
912 [coder encodeValueOfObjCType:@encode(typeof(isCoupled)) at:&isCoupled];
913 }
914}
915
916- (id)initWithCoder:(NSCoder *)coder {
917 if ((self = [super initWithCoder:coder])) {
918 NSData* data = nil0;
919 float divt = 0.0;
920 isCoupled = YES( BOOL ) 1;
921 isDragging = NO( BOOL ) 0;
922 isInScrollView = NO( BOOL ) 0;
923 canSaveState = NO( BOOL ) 0;
924 if ([coder allowsKeyedCoding]) {
925 isCoupled = [coder decodeBoolForKey:@"isCoupled"];
926 [self setDelegate:[coder decodeObjectForKey:@"delegate"]];
927 [self setAutosaveName:[coder decodeObjectForKey:@"autosaveName"] recursively:NO( BOOL ) 0];
928 data = [coder decodeObjectForKey:@"divider"];
929 [self setBackground:[coder decodeObjectForKey:@"background"]];
930 divt = [coder decodeFloatForKey:@"dividerThickness"];
931 isHorizontal = [coder decodeBoolForKey:@"isHorizontal"];
932 } else {
933 [self setDelegate:[coder decodeObject]];
934 [self setAutosaveName:[coder decodeObject] recursively:NO( BOOL ) 0];
935 data = [coder decodeObject];
936 [self setBackground:[coder decodeObject]];
937 [coder decodeValueOfObjCType:@encode(typeof(divt)) at:&divt];
938 [coder decodeValueOfObjCType:@encode(typeof(isHorizontal)) at:&isHorizontal];
939 [coder decodeValueOfObjCType:@encode(typeof(isCoupled)) at:&isCoupled];
940 }
941 dividers = NULL( ( void * ) 0 );
942 if (data) {
943 NSBitmapImageRep* rep = [NSBitmapImageRep imageRepWithData:data];
944 NSImage* image = [[[NSImage alloc] initWithSize:[rep size]] autorelease];
945 [image setFlipped:YES( BOOL ) 1];
946 [image addRepresentation:rep];
947 [self setDivider:image];
948 } else {
949 [self setDivider:nil0];
950 }
951 [self setDividerThickness:divt];
952 [self setMustAdjust];
953 [self performSelector:@selector(viewDidMoveToSuperview) withObject:nil0 afterDelay:0.0];
954 [self performSelector:@selector(RB___adjustOutermostIfNeeded) withObject:nil0 afterDelay:0.0];
955 }
956 return self;
957}
958
959@end
960
961@implementation RBSplitView (RB___ViewAdditions)
962
963// This sets the dragging status flag. After clearing the flag, the state must be saved explicitly.
964- (void)RB___setDragging:(BOOL)flag {
965 BOOL