Oolite 1.91.0.7604-240417-a536cbe
Loading...
Searching...
No Matches
RBSplitView.m
Go to the documentation of this file.
1//
2// RBSplitView.m version 1.2
3// RBSplitView
4//
5// Created by Rainer Brockerhoff on 24/09/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// Please don't remove this copyright notice!
14static const unsigned char RBSplitView_Copyright[] __attribute__ ((used)) =
15 "RBSplitView 1.2 Copyright(c)2004-2009 by Rainer Brockerhoff <rainer@brockerhoff.net>.";
16
17// This vector keeps currently used cursors. nil means the default cursor.
18static NSCursor* cursors[RBSVCursorTypeCount] = {nil};
19
20// Our own fMIN and fMAX
21static inline CGFloat fMIN(CGFloat a,CGFloat b) {
22 return a<b?a:b;
23}
24
25static inline CGFloat fMAX(CGFloat a,CGFloat 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) {
41 return [NSCursor resizeUpDownCursor];
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]];
67 [defaults removeObjectForKey:[[self class] defaultsKeyForName:name isHorizontal:YES]];
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// These methods get and set 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 NSUInteger subcount = [subviews count];
98 NSUInteger i;
99 for (i=0;i<subcount;i++) {
100 RBSplitView* sv = [[subviews objectAtIndex:i] asSplitView];
101 if (sv) {
102 NSString* subst = clear?@"":[aString stringByAppendingFormat:@"[%ld]",i];
103 [sv setAutosaveName:subst recursively:YES];
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 for (RBSplitSubview* sub in [self subviews]){
118 [[sub asSplitView] saveState:YES];
119 }
120 }
121 return YES;
122 }
123 return NO;
124}
125
126// Restores the saved state of the subviews if there's a valid autosave name set. If the argument
127// is YES, it's also called recursively for nested RBSplitViews. Returns YES if successful.
128// It's good policy to call adjustSubviews immediately after calling restoreState.
129- (BOOL)restoreState:(BOOL)recurse {
130 BOOL result = NO;
131 if ([autosaveName length]) {
132 result = [self setStateFromString:[[NSUserDefaults standardUserDefaults] stringForKey:[[self class] defaultsKeyForName:autosaveName isHorizontal:[self isHorizontal]]]];
133 if (result&&recurse) {
134 for (RBSplitSubview* sub in [self subviews]){
135 [[sub asSplitView] restoreState:YES];
136 }
137 }
138 }
139 canSaveState = YES;
140 return result;
141}
142
143// Returns an array with complete state information for the receiver and all subviews, taking
144// nesting into account. Don't store this array in a file, as its format might change in the
145// future; this is for taking a state snapshot and later restoring it with setStatesFromArray.
146- (NSArray*)arrayWithStates {
147 NSMutableArray* array = [NSMutableArray array];
148 [array addObject:[self stringWithSavedState]];
149 for (RBSplitSubview* sub in [self subviews]){
150 RBSplitView* suv = [sub asSplitView];
151 if (suv) {
152 [array addObject:[suv arrayWithStates]];
153 } else {
154 [array addObject:[NSNull null]];
155 }
156 }
157 return array;
158}
159
160// Restores the state of the receiver and all subviews. The array must have been produced by a
161// previous call to arrayWithStates. Returns YES if successful. This will fail if you have
162// added or removed subviews in the meantime!
163// You need to call adjustSubviews after calling this.
164- (BOOL)setStatesFromArray:(NSArray*)array {
165 NSArray* subviews = [self subviews];
166 NSUInteger count = [array count];
167 if (count==([subviews count]+1)) {
168 NSString* me = [array objectAtIndex:0];
169 if ([me isKindOfClass:[NSString class]]) {
170 if ([self setStateFromString:me]) {
171 NSUInteger i;
172 for (i=1;i<count;i++) {
173 NSArray* item = [array objectAtIndex:i];
174 RBSplitView* suv = [[subviews objectAtIndex:i-1] asSplitView];
175 if ([item isKindOfClass:[NSArray class]]==(suv!=nil)) {
176 if (suv&&![suv setStatesFromArray:item]) {
177 return NO;
178 }
179 } else {
180 return NO;
181 }
182 }
183 return YES;
184 }
185 }
186 }
187 return NO;
188}
189
190// Returns a string encoding the current state of all direct subviews. Does not check for nesting.
191// The string contains the number of direct subviews, then the dimension for each subview (which will
192// be negative for collapsed subviews), all separated by blanks.
193- (NSString*)stringWithSavedState {
194 NSArray* subviews = [self subviews];
195 NSMutableString* result = [NSMutableString stringWithFormat:@"%ld",[subviews count]];
196 for (RBSplitSubview* sub in [self subviews]){
197 double size = [sub dimension];
198 if ([sub isCollapsed]) {
199 size = -size;
200 } else {
201 size += +[sub RB___fraction];
202 }
203 [result appendFormat:[sub isHidden]?@" %gH":@" %g",size];
204 }
205 return result;
206}
207
208// Readjusts all direct subviews according to the encoded string parameter.
209// The number of subviews must match. Returns YES if successful. Does not check for nesting.
210- (BOOL)setStateFromString:(NSString*)aString {
211 if ([aString length]) {
212 NSArray* parts = [aString componentsSeparatedByString:@" "];
213 NSArray* subviews = [self subviews];
214 NSInteger subcount = [subviews count];
215 NSInteger k = [parts count];
216 if ((k-->1)&&([[parts objectAtIndex:0] intValue]==subcount)&&(k==subcount)) {
217 NSInteger i;
218 NSRect frame = [self frame];
219 BOOL ishor = [self isHorizontal];
220 for (i=0;i<subcount;i++) {
221 NSString* part = [parts objectAtIndex:i+1];
222 double size = [part doubleValue];
223 BOOL hidden = [part hasSuffix:@"H"];
224 BOOL negative = size<=0.0;
225 if (negative) {
226 size = -size;
227 }
228 double fract = size;
229 size = floor(size);
230 fract -= size;
231 DIM(frame.size) = size;
232 RBSplitSubview* sub = [subviews objectAtIndex:i];
233 [sub RB___setFrame:frame withFraction:fract notify:NO];
234 if (negative) {
235 [sub RB___collapse];
236 }
237 [sub RB___setHidden:hidden];
238 }
239 [self setMustAdjust];
240 return YES;
241 }
242 }
243 return NO;
244}
245
246// This is the designated initializer for creating RBSplitViews programmatically. You can set the
247// divider image and other parameters afterwards.
248- (id)initWithFrame:(NSRect)frame {
249 self = [super initWithFrame:frame];
250 if (self) {
251 dividers = NULL;
252 isCoupled = YES;
253 isDragging = NO;
254 isInScrollView = NO;
255 canSaveState = NO;
256 [self setVertical:YES];
257 [self setDivider:nil];
258 [self setAutosaveName:nil recursively:NO];
259 [self setBackground:nil];
260 }
261 return self;
262}
263
264// This convenience initializer adds any number of subviews and adjusts them proportionally.
265- (id)initWithFrame:(NSRect)frame andSubviews:(NSUInteger)count {
266 self = [self initWithFrame:frame];
267 if (self) {
268 while (count-->0) {
269 [self addSubview:[[[RBSplitSubview alloc] initWithFrame:frame] autorelease]];
270 }
271 [self setMustAdjust];
272 }
273 return self;
274}
275
276// Frees retained objects when going away.
277- (void)dealloc {
278 if (dividers) {
279 free(dividers);
280 }
281 [autosaveName release];
282 [divider release];
283 [background release];
284 [super dealloc];
285}
286
287// Sets and gets the coupling between the view and its containing RBSplitView (if any). Coupled
288// RBSplitViews take some parameters, such as divider images, from the containing view. The default
289// is for nested RBSplitViews is YES; however, isCoupled returns NO if we're not nested.
290- (void)setCoupled:(BOOL)flag {
291 if (flag!=isCoupled) {
292 isCoupled = flag;
293// If we've just been uncoupled and there's no divider image, we copy it from the containing view.
294 if (!isCoupled&&!divider) {
295 [self setDivider:[[self splitView] divider]];
296 }
297 [self setMustAdjust];
298 }
299}
300
301- (BOOL)isCoupled {
302 return isCoupled&&([super splitView]!=nil);
303}
304
305// This returns the containing splitview if they are coupled. It's guaranteed to return a RBSplitView or nil.
307 return isCoupled?[super couplingSplitView]:nil;
308}
309
310// This returns self.
312 return self;
313}
314
315// This return self if we're really coupled to the owning splitview.
317 return [self isCoupled]?self:nil;
318}
319
320// We always return NO, but do special handling in RBSplitSubview's mouseDown: method.
322 return NO;
323}
324
325// RBSplitViews must be flipped to work properly for horizontal dividers. As the subviews are never
326// flipped, this won't make your life harder.
327- (BOOL)isFlipped {
328 return YES;
329}
330
331// Call this method to make sure that the subviews and divider rectangles are recalculated
332// properly before display.
333- (void)setMustAdjust {
334 mustAdjust = YES;
335 [self setNeedsDisplay:YES];
336}
337
338// Returns YES if there's a pending adjustment.
339- (BOOL)mustAdjust {
340 return mustAdjust;
341}
342
343// Returns YES if we're in a dragging loop.
344- (BOOL)isDragging {
345 return isDragging;
346}
347
348// Returns YES if the view is directly contained in an NSScrollView.
349- (BOOL)isInScrollView {
350 return isInScrollView;
351}
352
353// This pair of methods allows you to move the dividers for background windows while holding down
354// the command key, without bringing the window to the foreground.
355- (BOOL)acceptsFirstMouse:(NSEvent*)theEvent {
356 return ([theEvent modifierFlags]&NSCommandKeyMask)==0;
357}
358
359- (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent*)theEvent {
360 return ([theEvent modifierFlags]&NSCommandKeyMask)!=0;
361}
362
363// These 3 methods handle view background colors and opacity. The default is the window background.
364// Pass nil or a completely transparent color to setBackground to use transparency. If you set any
365// other background color, it will completely fill the RBSplitView (including subviews and dividers).
366// The view will be considered opaque only if its alpha is equal to 1.0.
367// For a nested, coupled RBSplitView, background and opacity are copied from the containing RBSplitView,
368// and setting the background has no effect.
369- (NSColor*)background {
370 RBSplitView* sv = [self couplingSplitView];
371 return sv?[sv background]:background;
372}
373
374- (void)setBackground:(NSColor*)color {
375 if (![self couplingSplitView]) {
376 [background autorelease];
377 background = color?([color alphaComponent]>0.0?[color retain]:nil):nil;
378 [self setNeedsDisplay:YES];
379 }
380}
381
382- (BOOL)isOpaque {
383 RBSplitView* sv = [self couplingSplitView];
384 return sv?[sv isOpaque]:(background&&([background alphaComponent]>=1.0));
385}
386
387// This will make debugging a little easier by appending the state string to the
388// default description.
389- (NSString*)description {
390 return [NSString stringWithFormat:@"%@ {%@}",[super description],[self stringWithSavedState]];
391}
392
393// The following 4 methods handle divider orientation. The actual stored trait is horizontality,
394// but verticality is used for setting to conform to the NSSplitView convention.
395// For a nested RBSplitView, orientation is perpendicular to the containing RBSplitView, and
396// setting it has no effect.
397// After changing the orientation you may want to restore the state with restoreState:.
398- (BOOL)isHorizontal {
399 RBSplitView* sv = [self splitView];
400 return sv?[sv isVertical]:isHorizontal;
401}
402
403- (void)setHorizontal:(BOOL)flag {
404 if (![self splitView]&&(isHorizontal!=flag)) {
405 BOOL ishor = isHorizontal = flag;
406 NSSize size = divider?[divider size]:NSZeroSize;
407 [self setDividerThickness:DIM(size)];
408 [self setMustAdjust];
409 }
410}
411
412- (BOOL)isVertical {
413 return ![self isHorizontal];
414}
415
416- (void)setVertical:(BOOL)flag {
417 [self setHorizontal:!flag];
418}
419
420// Returns the subview which a given identifier.
421- (RBSplitSubview*)subviewWithIdentifier:(NSString*)anIdentifier {
422 for (RBSplitSubview* sub in [self subviews]){
423 if ([anIdentifier isEqualToString:[sub identifier]]) {
424 return sub;
425 }
426 }
427 return nil;
428}
429
430// Returns the subview at a given position
431- (RBSplitSubview*)subviewAtPosition:(NSUInteger)position {
432 NSArray* subviews = [super subviews];
433 NSUInteger subcount = [subviews count];
434 if (position<subcount) {
435 return [subviews objectAtIndex:position];
436 }
437 return nil;
438}
439
440// This pair of methods gets and sets the delegate object. Delegates aren't retained.
441- (id)delegate {
442 return delegate;
443}
444
445- (void)setDelegate:(id)anObject {
446 delegate = anObject;
447}
448
449// This pair of methods gets and sets the divider image. Setting the image automatically adjusts the
450// divider thickness. A nil image means a 0-pixel wide divider, unless you set a thickness explicitly.
451// For a nested RBSplitView, the divider is copied from the containing RBSplitView, and
452// setting it has no effect. The returned image is always flipped.
453- (NSImage*)divider {
454 RBSplitView* sv = [self couplingSplitView];
455 return sv?[sv divider]:divider;
456}
457
458- (void)setDivider:(NSImage*)image {
459 if (![self couplingSplitView]) {
460 [divider autorelease];
461 if ([image isFlipped]) {
462// If the image is flipped, we just retain it.
463 divider = [image retain];
464 } else {
465// if the image isn't flipped, we copy the image instead of retaining it, and flip the copy.
466 divider = [image copy];
467 [divider setFlipped:YES];
468 }
469// We set the thickness to 0.0 so the image dimension will prevail.
470 [self setDividerThickness:0.0];
471 [self setMustAdjust];
472 }
473}
474
475// This pair of methods gets and sets the divider thickness. It should be an integer value and at least
476// 0.0, so we make sure. Set it to 0.0 to make the image dimensions prevail.
477- (CGFloat)dividerThickness {
478 if (dividerThickness>0.0) {
479 return dividerThickness;
480 }
481 NSImage* divdr = [self divider];
482 if (divdr) {
483 NSSize size = [divdr size];
484 BOOL ishor = [self isHorizontal];
485 return DIM(size);
486 }
487 return 0.0;
488}
489
490- (void)setDividerThickness:(CGFloat)thickness {
491 CGFloat t = fMAX(0.0,floor(thickness));
492 if ((NSInteger)dividerThickness!=(NSInteger)t) {
494 [self setMustAdjust];
495 }
496}
497
498// These three methods add subviews. If aView isn't a RBSplitSubview, one is automatically inserted above
499// it, and aView's frame and resizing mask is set to occupy the entire RBSplitSubview.
500- (void)addSubview:(NSView*)aView {
501 if ([aView isKindOfClass:[RBSplitSubview class]]) {
502 [super addSubview:aView];
503 } else {
504 [aView setFrameOrigin:NSZeroPoint];
505 RBSplitSubview* sub = [[[RBSplitSubview alloc] initWithFrame:[aView frame]] autorelease];
506 [aView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
507 [sub addSubview:aView];
508 [super addSubview:sub];
509 }
510 [self setMustAdjust];
511}
512
513- (void)addSubview:(NSView*)aView positioned:(NSWindowOrderingMode)place relativeTo:(NSView*)otherView {
514 if ([aView isKindOfClass:[RBSplitSubview class]]) {
515 [super addSubview:aView positioned:place relativeTo:otherView];
516 } else {
517 [aView setFrameOrigin:NSZeroPoint];
518 RBSplitSubview* sub = [[[RBSplitSubview alloc] initWithFrame:[aView frame]] autorelease];
519 [aView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
520 [sub addSubview:aView];
521 [super addSubview:sub positioned:place relativeTo:otherView];
522 [aView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
523 }
524 [self setMustAdjust];
525}
526
527- (void)addSubview:(NSView*)aView atPosition:(NSUInteger)position {
528 RBSplitSubview* suv = [self subviewAtPosition:position];
529 if (suv) {
530 [self addSubview:aView positioned:NSWindowBelow relativeTo:suv];
531 } else {
532 [self addSubview:aView];
533 }
534}
535
536// This keeps the isInScrollView flag up-to-date.
538 [super viewDidMoveToSuperview];
539 NSScrollView* scrollv = [self enclosingScrollView];
540 isInScrollView = scrollv?[scrollv documentView]==self:NO;
541}
542
543// This makes sure the subviews are adjusted after a subview is removed.
544- (void)willRemoveSubview:(NSView*)subview {
545 if ([subview respondsToSelector:@selector(RB___stopAnimation)]) {
546 [(RBSplitSubview*)subview RB___stopAnimation];
547 }
548 [super willRemoveSubview:subview];
549 [self setMustAdjust];
550}
551
552// RBSplitViews never resize their subviews automatically.
553- (BOOL)autoresizesSubviews {
554 return NO;
555}
556
557// This adjusts the subviews when the size is set. setFrame: calls this, so all is well. It calls
558// the delegate if implemented.
559- (void)setFrameSize:(NSSize)size {
560// NSLog(@"setFrameSize of %@ to %@",self,NSStringFromSize(size));
561 NSSize oldsize = [self frame].size;
562 [super setFrameSize:size];
563 [self setMustAdjust];
564 if ([delegate respondsToSelector:@selector(splitView:wasResizedFrom:to:)]) {
565 BOOL ishor = [self isHorizontal];
566 CGFloat olddim = DIM(oldsize);
567 CGFloat newdim = DIM(size);
568// The delegate is not called if the dimension hasn't changed.
569 if (((NSInteger)newdim!=(NSInteger)olddim)) {
570 [delegate splitView:self wasResizedFrom:olddim to:newdim];
571 }
572 }
573// We adjust the subviews only if the delegate didn't.
574 if (mustAdjust&&!isAdjusting) {
575 [self adjustSubviews];
576 }
577}
578
579// This method handles dragging and double-clicking dividers with the mouse. While dragging, the
580// "closed hand" cursor is shown. Double clicks are handled separately. Nothing will happen if
581// no divider image is set.
582- (void)mouseDown:(NSEvent*)theEvent {
583 if (!dividers) {
584 return;
585 }
586 NSArray* subviews = [self RB___subviews];
587 NSUInteger subcount = [subviews count];
588 if (subcount<2) {
589 return;
590 }
591// If the mousedown was in an alternate dragview, or if there's no divider image, handle it in RBSplitSubview.
592 if ((actDivider<NSNotFound)||![self divider]) {
593 [super mouseDown:theEvent];
594 return;
595 }
596 NSPoint where = [self convertPoint:[theEvent locationInWindow] fromView:nil];
597 BOOL ishor = [self isHorizontal];
598 NSUInteger i;
599 --subcount;
600// Loop over the divider rectangles.
601 for (i=0;i<subcount;i++) {
602 NSRect* divdr = &dividers[i];
603 if ([self mouse:where inRect:*divdr]) {
604// leading points at the subview immediately leading the divider being tracked.
605 RBSplitView* leading = [subviews objectAtIndex:i];
606// trailing points at the subview immediately trailing the divider being tracked.
607 RBSplitView* trailing = [subviews objectAtIndex:i+1];
608 if ([delegate respondsToSelector:@selector(splitView:shouldHandleEvent:inDivider:betweenView:andView:)]) {
609 if (![delegate splitView:self shouldHandleEvent:theEvent inDivider:i betweenView:leading andView:trailing]) {
610 return;
611 }
612 }
613// If it's a double click, try to expand or collapse one of the neighboring subviews.
614 if ([theEvent clickCount]>1) {
615// If both are collapsed, we do nothing. If one of them is collapsed, we try to expand it.
616 if ([trailing isCollapsed]) {
617 if (![leading isCollapsed]) {
618 [self RB___tryToExpandTrailing:trailing leading:leading delta:-[trailing dimension]];
619 }
620 } else {
621 if ([leading isCollapsed]) {
622 [self RB___tryToExpandLeading:leading divider:i trailing:trailing delta:[leading dimension]];
623 } else {
624// If neither are collapsed, we check if both are collapsible.
625 BOOL lcan = [leading canCollapse];
626 BOOL tcan = [trailing canCollapse];
627 CGFloat ldim = [leading dimension];
628 if (lcan&&tcan) {
629// If both are collapsible, we try asking the delegate.
630 if ([delegate respondsToSelector:@selector(splitView:collapseLeading:orTrailing:)]) {
631 RBSplitSubview* sub = [delegate splitView:self collapseLeading:leading orTrailing:trailing];
632// If the delegate returns nil, neither view will collapse.
633 lcan = sub==leading;
634 tcan = sub==trailing;
635 } else {
636// Otherwise we try collapsing the smaller one. If they're equal, the trailing one will be collapsed.
637 lcan = ldim<[trailing dimension];
638 }
639 }
640// At this point, we'll try to collapse the leading subview.
641 if (lcan) {
642 [self RB___tryToShortenLeading:leading divider:i trailing:trailing delta:-ldim always:NO];
643 }
644// If the leading subview didn't collapse for some reason, we try to collapse the trailing one.
645 if (!mustAdjust&&tcan) {
646 [self RB___tryToShortenTrailing:trailing divider:i leading:leading delta:[trailing dimension] always:NO];
647 }
648 }
649 }
650// If the subviews have changed, clear the fractions, adjust and redisplay
651 if (mustAdjust) {
652 [self RB___setMustClearFractions];
653 RBSplitView* sv = [self splitView];
654 [sv?sv:self adjustSubviews];
655 [super display];
656 }
657 } else {
658// Single click; record the offsets within the divider rectangle and check for nesting.
659 CGFloat divt = [self dividerThickness];
660 CGFloat offset = DIM(where)-DIM(divdr->origin);
661// Check if the leading subview is nested and if yes, if one of its two-axis thumbs was hit.
662 NSInteger ldivdr = NSNotFound;
663 CGFloat loffset = 0.0;
664 NSPoint lwhere = where;
665 NSRect lrect = NSZeroRect;
666 if ((leading = [leading coupledSplitView])) {
667 ldivdr = [leading RB___dividerHitBy:lwhere relativeToView:self thickness:divt];
668 if (ldivdr!=NSNotFound) {
669 lrect = [leading RB___dividerRect:ldivdr relativeToView:self];
670 loffset = OTHER(lwhere)-OTHER(lrect.origin);
671 }
672 }
673// Check if the trailing subview is nested and if yes, if one of its two-axis thumbs was hit.
674 NSInteger tdivdr = NSNotFound;
675 CGFloat toffset = 0.0;
676 NSPoint twhere = where;
677 NSRect trect = NSZeroRect;
678 if ((trailing = [trailing coupledSplitView])) {
679 tdivdr = [trailing RB___dividerHitBy:twhere relativeToView:self thickness:divt];
680 if (tdivdr!=NSNotFound) {
681 trect = [trailing RB___dividerRect:tdivdr relativeToView:self];
682 toffset = OTHER(twhere)-OTHER(trect.origin);
683 }
684 }
685// Now we loop handling mouse events until we get a mouse up event, while showing the drag cursor.
686 [[RBSplitView cursor:RBSVDragCursor] push];
687 [self RB___setDragging:YES];
688 while ((theEvent = [NSApp nextEventMatchingMask:NSLeftMouseDownMask|NSLeftMouseDraggedMask|NSLeftMouseUpMask untilDate:[NSDate distantFuture] inMode:NSEventTrackingRunLoopMode dequeue:YES])&&([theEvent type]!=NSLeftMouseUp)) {
689// Set up a local autorelease pool for the loop to prevent buildup of temporary objects.
690 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
691 NSDisableScreenUpdates();
692// Track the mouse along the main coordinate.
693 [self RB___trackMouseEvent:theEvent from:where withBase:NSZeroPoint inDivider:i];
694 if (ldivdr!=NSNotFound) {
695// Track any two-axis thumbs for the leading nested RBSplitView.
696 [leading RB___trackMouseEvent:theEvent from:[self convertPoint:lwhere toView:leading] withBase:NSZeroPoint inDivider:ldivdr];
697 }
698 if (tdivdr!=NSNotFound) {
699// Track any two-axis thumbs for the trailing nested RBSplitView.
700 [trailing RB___trackMouseEvent:theEvent from:[self convertPoint:twhere toView:trailing] withBase:NSZeroPoint inDivider:tdivdr];
701 }
702 if (mustAdjust||[leading mustAdjust]||[trailing mustAdjust]) {
703// The mouse was dragged and the subviews changed, so we must redisplay, as
704// several divider rectangles may have changed.
705 RBSplitView* sv = [self splitView];
706 [sv?sv:self adjustSubviews];
707 [super display];
708 divdr = &dividers[i];
709// Adjust to the new cursor coordinates.
710 DIM(where) = DIM(divdr->origin)+offset;
711 if ((ldivdr!=NSNotFound)&&![leading isCollapsed]) {
712// Adjust for the leading nested RBSplitView's thumbs while it's not collapsed.
713 lrect = [leading RB___dividerRect:ldivdr relativeToView:self];
714 OTHER(lwhere) = OTHER(lrect.origin)+loffset;
715 }
716 if ((tdivdr!=NSNotFound)&&![trailing isCollapsed]) {
717// Adjust for the trailing nested RBSplitView's thumbs while it's not collapsed.
718 trect = [trailing RB___dividerRect:tdivdr relativeToView:self];
719 OTHER(twhere) = OTHER(trect.origin)+toffset;
720 }
721 }
722 NSEnableScreenUpdates();
723 [pool drain];
724 }
725 [self RB___setDragging:NO];
726// Redisplay the previous cursor.
727 [NSCursor pop];
728 }
729 }
730 }
731}
732
733// This will be called before the view will be redisplayed, so we adjust subviews if necessary.
734- (BOOL)needsDisplay {
735 if (mustAdjust&&!isAdjusting) {
736 [self adjustSubviews];
737 return YES;
738 }
739 return [super needsDisplay];
740}
741
742// We implement awakeFromNib to restore the state. This works if an autosaveName is set in the nib.
743- (void)awakeFromNib {
744 if ([RBSplitSubview instancesRespondToSelector:@selector(awakeFromNib)]) {
745 [super awakeFromNib];
746 }
747 if (![self splitView]) {
748 [self restoreState:YES];
749 }
750}
751
752// We check if subviews must be adjusted before redisplaying programmatically.
753- (void)display {
754 if (mustAdjust&&!isAdjusting) {
755 [self adjustSubviews];
756 }
757 [super display];
758}
759
760// This method draws the divider rectangles and then the two-axis thumbs if there are any.
761- (void)drawRect:(NSRect)rect {
762 [super drawRect:rect];
763 if (!dividers) {
764 return;
765 }
766 NSArray* subviews = [self RB___subviews];
767 NSUInteger subcount = [subviews count];
768// Return if there are no dividers to draw.
769 if (subcount<2) {
770 return;
771 }
772 --subcount;
773 NSUInteger i;
774// Cache the divider image.
775 NSImage* divdr = [self divider];
776 CGFloat divt = [self dividerThickness];
777// Loop over the divider rectangles.
778 for (i=0;i<subcount;i++) {
779// Check if we need to draw this particular divider.
780 if ([self needsToDrawRect:dividers[i]]) {
781 RBSplitView* leading = [subviews objectAtIndex:i];
782 RBSplitView* trailing = [subviews objectAtIndex:i+1];
783 BOOL lexp = divdr?![leading isCollapsed]:NO;
784 BOOL texp = divdr?![trailing isCollapsed]:NO;
785// We don't draw the divider image if either of the neighboring subviews is a non-collapsed
786// nested split view.
787 BOOL nodiv = (lexp&&[leading coupledSplitView])||(texp&&[trailing coupledSplitView]);
788 [self drawDivider:nodiv?nil:divdr inRect:dividers[i] betweenView:leading andView:trailing];
789 if (divdr) {
790// Draw the corresponding two-axis thumbs if the leading view is a nested RBSplitView.
791 if ((leading = [leading coupledSplitView])&&lexp) {
792 [leading RB___drawDividersIn:self forDividerRect:dividers[i] thickness:divt];
793 }
794// Draw the corresponding two-axis thumbs if the trailing view is a nested RBSplitView.
795 if ((trailing = [trailing coupledSplitView])&&texp) {
796 [trailing RB___drawDividersIn:self forDividerRect:dividers[i] thickness:divt];
797 }
798 }
799 }
800 }
801}
802
803// This method draws dividers. You should never call it directly but you can override it when
804// subclassing, if you need custom dividers. It draws the divider image centered in the divider rectangle.
805// If we're drawing a two-axis thumb leading and trailing will be nil, and the rectangle
806// will be the thumb rectangle.
807// If there are nested split views this will be called once to draw the main divider rect,
808// and again for every thumb.
809- (void)drawDivider:(NSImage*)anImage inRect:(NSRect)rect betweenView:(RBSplitSubview*)leading andView:(RBSplitSubview*)trailing {
810// Fill the view with the background color (if there's any). Don't draw the background again for
811// thumbs.
812 if (leading||trailing) {
813 NSColor* bg = [self background];
814 if (bg) {
815 [bg set];
816 NSRectFillUsingOperation(rect,NSCompositeSourceOver);
817 }
818 }
819// Center the image, if there is one.
820 NSRect imrect = NSZeroRect;
821 NSRect dorect = NSZeroRect;
822 if (anImage) {
823 imrect.size = dorect.size = [anImage size];
824 dorect.origin = NSMakePoint(floor(rect.origin.x+(rect.size.width-dorect.size.width)/2),
825 floor(rect.origin.y+(rect.size.height-dorect.size.height)/2));
826 }
827// Ask the delegate for the final rect where the image should be drawn.
828 if ([delegate respondsToSelector:@selector(splitView:willDrawDividerInRect:betweenView:andView:withProposedRect:)]) {
829 dorect = [delegate splitView:self willDrawDividerInRect:rect betweenView:leading andView:trailing withProposedRect:dorect];
830 }
831// Draw the image if the delegate returned a non-empty rect.
832 if (!NSIsEmptyRect(dorect)) {
833 [anImage drawInRect:dorect fromRect:imrect operation:NSCompositeSourceOver fraction:1.0];
834 }
835}
836
837// This method should be called only from within the splitView:wasResizedFrom:to: delegate method
838// to keep some specific subview the same size.
839- (void)adjustSubviewsExcepting:(RBSplitSubview*)excepting {
840 [self RB___adjustSubviewsExcepting:[excepting isCollapsed]?nil:excepting];
841}
842
843// This method adjusts subviews and divider rectangles.
844- (void)adjustSubviews {
845 [self RB___adjustSubviewsExcepting:nil];
846}
847
848// This resets the appropriate cursors for each divider according to the orientation.
849// No cursors are shown if there is no divider image.
850- (void)resetCursorRects {
851 if (!dividers) {
852 return;
853 }
854 id del = [delegate respondsToSelector:@selector(splitView:cursorRect:forDivider:)]?delegate:nil;
855 NSArray* subviews = [self RB___subviews];
856 NSInteger divcount = [subviews count]-1;
857 if ((divcount<1)||![self divider]) {
858 [del splitView:self cursorRect:NSZeroRect forDivider:0];
859 return;
860 }
861 NSInteger i;
862 NSCursor* cursor = [RBSplitView cursor:[self isVertical]?RBSVVerticalCursor:RBSVHorizontalCursor];
863 CGFloat divt = [self dividerThickness];
864 for (i=0;i<divcount;i++) {
865 RBSplitView* sub = [[subviews objectAtIndex:i] coupledSplitView];
866// If the leading subview is a nested RBSplitView, add the thumb rectangles first.
867 if (sub) {
868 [sub RB___addCursorRectsTo:self forDividerRect:dividers[i] thickness:divt];
869 }
870 sub = [[subviews objectAtIndex:i+1] coupledSplitView];
871// If the trailing 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// Now add the divider rectangle.
876 NSRect divrect = dividers[i];
877 if (del) {
878 divrect = [del splitView:self cursorRect:divrect forDivider:i];
879 }
880 if (!NSIsEmptyRect(divrect)) {
881 [self addCursorRect:divrect cursor:cursor];
882 }
883 }
884}
885
886// These two methods encode and decode RBSplitViews. One peculiarity is that we encode the divider image's
887// bitmap representation as data; this makes the nib files larger, but the user can just paste any image
888// into the RBSplitView inspector - or use the default divider image - without having to include it into the
889// project, too.
890- (void)encodeWithCoder:(NSCoder *)coder {
891 [super encodeWithCoder:coder];
892 if ([coder allowsKeyedCoding]) {
893 [coder encodeConditionalObject:delegate forKey:@"delegate"];
894 [coder encodeObject:autosaveName forKey:@"autosaveName"];
895 [coder encodeObject:[divider TIFFRepresentation] forKey:@"divider"];
896 [coder encodeObject:background forKey:@"background"];
897 [coder encodeDouble:dividerThickness forKey:@"dividerThickness"];
898 [coder encodeBool:isHorizontal forKey:@"isHorizontal"];
899 [coder encodeBool:isCoupled forKey:@"isCoupled"];
900 } else {
901 [coder encodeConditionalObject:delegate];
902 [coder encodeObject:autosaveName];
903 [coder encodeObject:[divider TIFFRepresentation]];
904 [coder encodeObject:background];
905 [coder encodeValueOfObjCType:@encode(typeof(dividerThickness)) at:&dividerThickness];
906 [coder encodeValueOfObjCType:@encode(typeof(isHorizontal)) at:&isHorizontal];
907 [coder encodeValueOfObjCType:@encode(typeof(isCoupled)) at:&isCoupled];
908 }
909}
910
911- (id)initWithCoder:(NSCoder *)coder {
912 if ((self = [super initWithCoder:coder])) {
913 NSData* data = nil;
914 CGFloat divt = 0.0;
915 isCoupled = YES;
916 isDragging = NO;
917 isInScrollView = NO;
918 canSaveState = NO;
919 if ([coder allowsKeyedCoding]) {
920 isCoupled = [coder decodeBoolForKey:@"isCoupled"];
921 [self setDelegate:[coder decodeObjectForKey:@"delegate"]];
922 [self setAutosaveName:[coder decodeObjectForKey:@"autosaveName"] recursively:NO];
923 data = [coder decodeObjectForKey:@"divider"];
924 [self setBackground:[coder decodeObjectForKey:@"background"]];
925 divt = [coder decodeDoubleForKey:@"dividerThickness"];
926 isHorizontal = [coder decodeBoolForKey:@"isHorizontal"];
927 } else {
928 [self setDelegate:[coder decodeObject]];
929 [self setAutosaveName:[coder decodeObject] recursively:NO];
930 data = [coder decodeObject];
931 [self setBackground:[coder decodeObject]];
932 [coder decodeValueOfObjCType:@encode(typeof(divt)) at:&divt];
933 [coder decodeValueOfObjCType:@encode(typeof(isHorizontal)) at:&isHorizontal];
934 [coder decodeValueOfObjCType:@encode(typeof(isCoupled)) at:&isCoupled];
935 }
936 dividers = NULL;
937 if (data) {
938 NSBitmapImageRep* rep = [NSBitmapImageRep imageRepWithData:data];
939 NSImage* image = [[[NSImage alloc] initWithSize:[rep size]] autorelease];
940 [image setFlipped:YES];
941 [image addRepresentation:rep];
942 [self setDivider:image];
943 } else {
944 [self setDivider:nil];
945 }
946 [self setDividerThickness:divt];
947 [self setMustAdjust];
948 [self performSelector:@selector(viewDidMoveToSuperview) withObject:nil afterDelay:0.0];
949 [self performSelector:@selector(RB___adjustOutermostIfNeeded) withObject:nil afterDelay:0.0];
950 }
951 return self;
952}
953
954- (BOOL)isAdjusting {
955 return isAdjusting;
956}
957
958@end
959
960@implementation RBSplitView (RB___ViewAdditions)
961
962// This sets the dragging status flag. After clearing the flag, the state must be saved explicitly.
963- (void)RB___setDragging:(BOOL)flag {
964 BOOL save = isDragging&&!flag;
965 isDragging = flag;
966 if (save) {
967 [self saveState:NO];
968 }
969}
970
971// This returns the number of visible subviews.
972- (NSUInteger)RB___numberOfSubviews {
973 NSUInteger result = 0;
974 for (RBSplitSubview* sub in [self subviews]) {
975 if (![sub isHidden]) {
976 ++result;
977 }
978 }
979 return result;
980}
981
982// This returns the origin coordinate of the Nth divider.
983- (CGFloat)RB___dividerOrigin:(NSUInteger)indx {
984 CGFloat result = 0.0;
985 if (dividers) {
986 BOOL ishor = [self isHorizontal];
987 result = DIM(dividers[indx].origin);
988 }
989 return result;
990}
991
992// This returns an array with all non-hidden subviews.
993- (NSArray*)RB___subviews {
994 NSMutableArray* result = [NSMutableArray arrayWithArray:[self subviews]];
995 NSInteger i;
996 for (i=[result count]-1;i>=0;i--) {
997 RBSplitSubview* view = [result objectAtIndex:i];
998 if ([view isHidden]) {
999 [result removeObjectAtIndex:i];
1000 }
1001 }
1002 return result;
1003}
1004
1005// This returns the actual value set in dividerThickness.
1006- (CGFloat)RB___dividerThickness {
1007 return dividerThickness;
1008}
1009
1010// This method returns the actual dimension occupied by the subviews; that is, without dividers.
1012 BOOL ishor = [self isHorizontal];
1013 NSSize size = [self frame].size;
1014 return fMAX(1.0,DIM(size)-[self dividerThickness]*([self RB___numberOfSubviews]-1));
1015}
1016
1017// This method returns one of the divider rectangles, or NSZeroRect if the index is invalid.
1018// If view is non-nil, the rect will be expressed in that view's coordinates. We assume
1019// that view is a superview of self.
1020- (NSRect)RB___dividerRect:(NSUInteger)indx relativeToView:(RBSplitView*)view {
1021 if (dividers&&(indx<[self RB___numberOfSubviews]-1)) {
1022 NSRect result = dividers[indx];
1023 if (view&&(view!=self)) {
1024 result = [self convertRect:result toView:view];
1025 }
1026 return result;
1027 }
1028 return NSZeroRect;
1029}
1030
1031// Returns the index of the divider hit by the point, or NSNotFound if none.
1032// point is in coordinates relative to view. delta is the divider thickness added
1033// to both ends of the divider rect, to accomodate two-axis thumbs.
1034- (NSUInteger)RB___dividerHitBy:(NSPoint)point relativeToView:(RBSplitView*)view thickness:(CGFloat)delta {
1035 if (!dividers) {
1036 return NSNotFound;
1037 }
1038 NSInteger divcount = [self RB___numberOfSubviews]-1;
1039 if (divcount<1) {
1040 return NSNotFound;
1041 }
1042 NSInteger i;
1043 BOOL ishor = [self isHorizontal];
1044 point = [self convertPoint:point fromView:view];
1045 for (i=0;i<divcount;i++) {
1046 NSRect divdr = dividers[i];
1047 OTHER(divdr.origin) -= delta;
1048 OTHER(divdr.size) += 2*delta;
1049 if ([self mouse:point inRect:divdr]) {
1050 return i;
1051 }
1052 }
1053 return NSNotFound;
1054}
1055
1056// This method sets a flag to clear all fractions before adjusting.
1058 mustClearFractions = YES;
1059}
1060
1061// This local method asks the delegate if we should resize the trailing subview or the window
1062// when a divider is dragged. Not called if we're inside an NSScrollView.
1063- (BOOL)RB___shouldResizeWindowForDivider:(NSUInteger)indx betweenView:(RBSplitSubview*)leading andView:(RBSplitSubview*)trailing willGrow:(BOOL)grow {
1064 if (!isInScrollView&&[delegate respondsToSelector:@selector(splitView:shouldResizeWindowForDivider:betweenView:andView:willGrow:)]) {
1065 return [delegate splitView:self shouldResizeWindowForDivider:indx betweenView:leading andView:trailing willGrow:grow];
1066 }
1067 return NO;
1068}
1069
1070// This local method tries to expand the leading subview (which is assumed to be collapsed). Delta should be positive.
1071- (void)RB___tryToExpandLeading:(RBSplitSubview*)leading divider:(NSUInteger)indx trailing:(RBSplitSubview*)trailing delta:(CGFloat)delta {
1072 NSWindow* window = nil;
1073 NSView* document = nil;
1074 NSSize maxsize = NSMakeSize(WAYOUT,WAYOUT);
1075 NSRect frame = NSZeroRect;
1076 NSRect screen = NSMakeRect(0,0,WAYOUT,WAYOUT);
1077 BOOL ishor = NO;
1078// First we ask the delegate, if there's any, if the window should resize.
1079 BOOL dowin = ([self RB___shouldResizeWindowForDivider:indx betweenView:leading andView:trailing willGrow:YES]);
1080 if (dowin) {
1081// We initialize the other local variables only if we need them for the window.
1082 ishor = [self isHorizontal];
1083 document = [[self enclosingScrollView] documentView];
1084 if (document) {
1085 frame = [document frame];
1086 } else {
1087 window = [self window];
1088 frame = [window frame];
1089 maxsize = [window maxSize];
1090 screen = [[NSScreen mainScreen] visibleFrame];
1091 }
1092 }
1093// The mouse has to move over half of the expanded size (plus hysteresis) and the expansion shouldn't
1094// reduce the trailing subview to less than its minimum size (or grow the window beyond its maximum).
1095 CGFloat limit = [leading minDimension];
1096 CGFloat dimension = 0.0;
1097 if (dowin) {
1098 CGFloat maxd = fMAX(0.0,(ishor?frame.origin.y-screen.origin.y:(screen.origin.x+screen.size.width)-(frame.origin.x+frame.size.width)));
1099 dimension = fMIN(DIM(maxsize)-DIM(frame.size),maxd);
1100 } else {
1101 dimension = trailing?[trailing dimension]:WAYOUT;
1102 }
1103 if (limit>dimension) {
1104 return;
1105 }
1106 if (!dowin&&trailing) {
1107 limit += [trailing minDimension];
1108 if (limit>dimension) {
1109// If the trailing subview is going below its minimum, we try to collapse it first.
1110// However, we don't collapse if that would cause the leading subview to become larger than its maximum.
1111 if (([trailing canCollapse])&&(delta>(0.5+HYSTERESIS)*dimension)&&([leading maxDimension]<=dimension)) {
1112 delta = -[trailing RB___collapse];
1113 [leading changeDimensionBy:delta mayCollapse:NO move:NO];
1114 }
1115 return;
1116 }
1117 }
1118// The leading subview may be expanded normally.
1119 delta = -[leading changeDimensionBy:delta mayCollapse:NO move:NO];
1120 if (dowin) {
1121// If it does expand, we widen the window.
1122 DIM(frame.size) -= delta;
1123 if (ishor) {
1124 DIM(frame.origin) += delta;
1125 }
1126 if (document) {
1127 [document setFrame:frame];
1128 [document setNeedsDisplay:YES];
1129 } else {
1130 [window setFrame:frame display:YES];
1131 }
1132 [self setMustAdjust];
1133 } else {
1134// If it does expand, we shorten the trailing subview.
1135 [trailing changeDimensionBy:delta mayCollapse:NO move:YES];
1136 }
1137}
1138
1139// This local method tries to shorten the leading subview. Both subviews are assumed to be expanded.
1140// delta should be negative. If always is NO, the subview will be shortened only if it might also be
1141// collapsed; otherwise, it's shortened as much as possible.
1142- (void)RB___tryToShortenLeading:(RBSplitSubview*)leading divider:(NSUInteger)indx trailing:(RBSplitSubview*)trailing delta:(CGFloat)delta always:(BOOL)always {
1143 NSWindow* window = nil;
1144 NSView* document = nil;
1145 NSSize minsize = NSZeroSize;
1146 NSRect frame = NSZeroRect;
1147 BOOL ishor = NO;
1148// First we ask the delegate, if there's any, if the window should resize.
1149 BOOL dowin = ([self RB___shouldResizeWindowForDivider:indx betweenView:leading andView:trailing willGrow:NO]);
1150 if (dowin) {
1151// We initialize the other local variables only if we need them for the window.
1152 ishor = [self isHorizontal];
1153 document = [[self enclosingScrollView] documentView];
1154 if (document) {
1155 frame = [document frame];
1156 } else {
1157 window = [self window];
1158 frame = [window frame];
1159 minsize = [window minSize];
1160 }
1161 }
1162// We avoid making the trailing subview larger than its maximum, or the window smaller than its minimum.
1163 CGFloat limit = 0.0;
1164 if (dowin) {
1165 limit = DIM(frame.size)-DIM(minsize);
1166 } else {
1167 limit = trailing?([trailing maxDimension]-[trailing dimension]):WAYOUT;
1168 }
1169 if (-delta>limit) {
1170 if (always) {
1171 delta = -limit;
1172 } else {
1173 return;
1174 }
1175 }
1176 BOOL okl = limit>=[leading dimension];
1177 if (always||okl) {
1178// Resize leading.
1179 delta = -[leading changeDimensionBy:delta mayCollapse:okl move:NO];
1180 if (dowin) {
1181// Resize the window.
1182 DIM(frame.size) -= delta;
1183 if (ishor) {
1184 DIM(frame.origin) += delta;
1185 }
1186 if (document) {
1187 [document setFrame:frame];
1188 [document setNeedsDisplay:YES];
1189 } else {
1190 [window setFrame:frame display:YES];
1191 }
1192 [self setMustAdjust];
1193 } else {
1194// Otherwise, resize trailing.
1195 [trailing changeDimensionBy:delta mayCollapse:NO move:YES];
1196 }
1197 }
1198}
1199
1200// This local method tries to shorten the trailing subview. Both subviews are assumed to be expanded.
1201// delta should be positive. If always is NO, the subview will be shortened only if it might also be
1202// collapsed; otherwise, it's shortened as much as possible.
1203- (void)RB___tryToShortenTrailing:(RBSplitSubview*)trailing divider:(NSUInteger)indx leading:(RBSplitSubview*)leading delta:(CGFloat)delta always:(BOOL)always {
1204 NSWindow* window = nil;
1205 NSView* document = nil;
1206 NSSize maxsize = NSMakeSize(WAYOUT,WAYOUT);
1207 NSRect frame = NSZeroRect;
1208 NSRect screen = NSMakeRect(0,0,WAYOUT,WAYOUT);
1209 BOOL ishor = NO;
1210// First we ask the delegate, if there's any, if the window should resize.
1211 BOOL dowin = ([self RB___shouldResizeWindowForDivider:indx betweenView:leading andView:trailing willGrow:YES]);
1212 if (dowin) {
1213// We initialize the other local variables only if we need them for the window.
1214 ishor = [self isHorizontal];
1215 document = [[self enclosingScrollView] documentView];
1216 if (document) {
1217 frame = [document frame];
1218 } else {
1219 window = [self window];
1220 frame = [window frame];
1221 maxsize = [window maxSize];
1222 screen = [[NSScreen mainScreen] visibleFrame];
1223 }
1224 }
1225// We avoid making the leading subview larger than its maximum, or the window larger than its maximum.
1226 CGFloat limit = 0.0;
1227 if (dowin) {
1228 CGFloat maxd = fMAX(0.0,(ishor?frame.origin.y-screen.origin.y:(screen.origin.x+screen.size.width)-(frame.origin.x+frame.size.width)));
1229 limit = fMIN(DIM(maxsize)-DIM(frame.size),maxd);
1230 } else {
1231 limit = [leading maxDimension]-[leading dimension];
1232 }
1233 if (delta>limit) {
1234 if (always) {
1235 delta = limit;
1236 } else {
1237 return;
1238 }
1239 }
1240 BOOL okl = dowin||(limit>=(trailing?[trailing dimension]:WAYOUT));
1241 if (always||okl) {
1242 if (dowin) {
1243// If we should resize the window, resize leading, then the window.
1244 delta = [leading changeDimensionBy:delta mayCollapse:NO move:NO];
1245 DIM(frame.size) += delta;
1246 if (ishor) {
1247 DIM(frame.origin) -= delta;
1248 }
1249 if (document) {
1250 [document setFrame:frame];
1251 [document setNeedsDisplay:YES];
1252 } else {
1253 [window setFrame:frame display:YES];
1254 }
1255 [self setMustAdjust];
1256 } else {
1257// Otherwise, resize trailing, then leading.
1258 if (trailing) {
1259 delta = -[trailing changeDimensionBy:-delta mayCollapse:okl move:YES];
1260 }
1261 [leading changeDimensionBy:delta mayCollapse:NO move:NO];
1262 }
1263 }
1264}
1265
1266// This method tries to expand the trailing subview (which is assumed to be collapsed).
1267- (void)RB___tryToExpandTrailing:(RBSplitSubview*)trailing leading:(RBSplitSubview*)leading delta:(CGFloat)delta {
1268// The mouse has to move over half of the expanded size (plus hysteresis) and the expansion shouldn't
1269// reduce the leading subview to less than its minimum size. If it does, we try to collapse it first.
1270// However, we don't collapse if that would cause the trailing subview to become larger than its maximum.
1271 CGFloat limit = trailing?[trailing minDimension]:0.0;
1272 CGFloat dimension = [leading dimension];
1273 if (limit>dimension) {
1274 return;
1275 }
1276 limit += [leading minDimension];
1277 if (limit>dimension) {
1278 if ([leading canCollapse]&&(-delta>(0.5+HYSTERESIS)*dimension)&&((trailing?[trailing maxDimension]:0.0)<=dimension)) {
1279 delta = -[leading RB___collapse];
1280 [trailing changeDimensionBy:delta mayCollapse:NO move:YES];
1281 }
1282 return;
1283 }
1284// The trailing subview may be expanded normally. If it does expand, we shorten the leading subview.
1285 if (trailing) {
1286 delta = -[trailing changeDimensionBy:-delta mayCollapse:NO move:YES];
1287 }
1288 [leading changeDimensionBy:delta mayCollapse:NO move:NO];
1289}
1290
1291
1292// This method is called by the mouseDown:method for every tracking event. It's separated out as it used
1293// to be called from the Interface Builder plugin in a slightly different way, and also if you have a
1294// separate drag view designated by the delegate. You'll never need to call this directly.
1295// theEvent is the event (which should be a NSLeftMouseDragged event).
1296// where is the point where the original mouse-down happened, corrected for the current divider position,
1297// and expressed in local coordinates.
1298// base is an offset (x,y) applied to the mouse location (usually will be zero)
1299// indx is the number of the divider that's being dragged.
1300- (void)RB___trackMouseEvent:(NSEvent*)theEvent from:(NSPoint)where withBase:(NSPoint)base inDivider:(NSUInteger)indx {
1301 NSArray* subviews = [self RB___subviews];
1302 NSUInteger subcount = [subviews count];
1303// Make sure that the divider number is valid.
1304 if (indx>=subcount) {
1305 return;
1306 }
1307 NSPoint result;
1308 NSUInteger k;
1309// leading and trailing point at the subviews immediately leading and trailing the divider being tracked
1310 RBSplitSubview* leading = [subviews objectAtIndex:indx];
1311 RBSplitSubview* trailing = [subviews objectAtIndex:indx+1];
1312// Convert the mouse coordinates to apply to the same system the divider rects are in.
1313 NSPoint mouse = [self convertPoint:[theEvent locationInWindow] fromView:nil];
1314 mouse.x -= base.x;
1315 mouse.y -= base.y;
1316 result.x = mouse.x-where.x;
1317 result.y = mouse.y-where.y;
1318// delta is the actual amount the mouse has moved in the relevant coordinate since the last event.
1319 BOOL ishor = [self isHorizontal];
1320 CGFloat delta = DIM(result);
1321 if (delta<0.0) {
1322// Negative delta means the mouse is being moved left or upwards.
1323// firstLeading will point at the first expanded subview to the left (or upwards) of the divider.
1324// If there's none (all subviews are collapsed) it will point at the nearest subview.
1325 RBSplitSubview* firstLeading = leading;
1326 k = indx;
1327 while (![firstLeading canShrink]) {
1328 if (k==0) {
1329 firstLeading = leading;
1330 break;
1331 }
1332 firstLeading = [subviews objectAtIndex:--k];
1333 }
1334 if (isInScrollView) {
1335 trailing = nil;
1336 }
1337// If the trailing subview is collapsed, it might be expanded if some conditions are met.
1338 if ([trailing isCollapsed]) {
1339 [self RB___tryToExpandTrailing:trailing leading:firstLeading delta:delta];
1340 } else {
1341 [self RB___tryToShortenLeading:firstLeading divider:indx trailing:trailing delta:delta always:YES];
1342 }
1343 } else if (delta>0.0) {
1344// Positive delta means the mouse is being moved right or downwards.
1345// firstTrailing will point at the first expanded subview to the right (or downwards) of the divider.
1346// If there's none (all subviews are collapsed) it will point at the nearest subview.
1347 RBSplitSubview* firstTrailing = nil;
1348 if (!isInScrollView) {
1349 firstTrailing = trailing;
1350 k = indx+1;
1351 while (![firstTrailing canShrink]) {
1352 if (++k>=subcount) {
1353 firstTrailing = trailing;
1354 break;
1355 }
1356 firstTrailing = [subviews objectAtIndex:k];
1357 }
1358 }
1359// If the leading subview is collapsed, it might be expanded if some conditions are met.
1360 if ([leading isCollapsed]) {
1361 [self RB___tryToExpandLeading:leading divider:indx trailing:firstTrailing delta:delta];
1362 } else {
1363// The leading subview is not collapsed, so we try to shorten or even collapse it
1364 [self RB___tryToShortenTrailing:firstTrailing divider:indx leading:leading delta:delta always:YES];
1365 }
1366 }
1367}
1368
1369// This is called for nested RBSplitViews, to add the cursor rects for the two-axis thumbs.
1370- (void)RB___addCursorRectsTo:(RBSplitView*)masterView forDividerRect:(NSRect)rect thickness:(CGFloat)delta {
1371 if (dividers&&[self divider]) {
1372 NSArray* subviews = [self RB___subviews];
1373 NSInteger divcount = [subviews count]-1;
1374 if (divcount<1) {
1375 return;
1376 }
1377 NSInteger i;
1378 NSCursor* cursor = [RBSplitView cursor:RBSV2WayCursor];
1379 BOOL ishor = [self isHorizontal];
1380// Loop over the divider rectangles, intersect them with the view's own, and add the thumb rectangle
1381// to the containing split view.
1382 for (i=0;i<divcount;i++) {
1383 NSRect divdr = dividers[i];
1384 divdr.origin = [self convertPoint:divdr.origin toView:masterView];
1385 OTHER(divdr.origin) -= delta;
1386 OTHER(divdr.size) += 2*delta;
1387 divdr = NSIntersectionRect(divdr,rect);
1388 if (!NSIsEmptyRect(divdr)) {
1389 [masterView addCursorRect:divdr cursor:cursor];
1390 }
1391 }
1392 }
1393}
1394
1395// This is called for nested RBSplitViews, to draw the two-axis thumbs.
1396- (void)RB___drawDividersIn:(RBSplitView*)masterView forDividerRect:(NSRect)rect thickness:(CGFloat)delta {
1397 if (!dividers) {
1398 return;
1399 }
1400 NSArray* subviews = [self RB___subviews];
1401 NSInteger divcount = [subviews count]-1;
1402 if (divcount<1) {
1403 return;
1404 }
1405 NSInteger i;
1406 BOOL ishor = [self isHorizontal];
1407// Get the outer split view's divider image.
1408 NSImage* image = [masterView divider];
1409// Loop over the divider rectangles, intersect them with the view's own, and draw the thumb there.
1410 for (i=0;i<divcount;i++) {
1411 NSRect divdr = dividers[i];
1412 divdr.origin = [self convertPoint:divdr.origin toView:masterView];
1413 OTHER(divdr.origin) -= delta;
1414 OTHER(divdr.size) += 2*delta;
1415 divdr = NSIntersectionRect(divdr,rect);
1416 if (!NSIsEmptyRect(divdr)) {
1417 [masterView drawDivider:image inRect:divdr betweenView:nil andView:nil];
1418 }
1419 }
1420}
1421
1422// This is usually called from initWithCoder to ensure that the outermost RBSplitView is
1423// properly adjusted when first displayed.
1425 RBSplitView* sv = [self splitView];
1426 if (sv) {
1428 return;
1429 }
1430 if (mustAdjust&&!isAdjusting) {
1431 [self adjustSubviews];
1432 }
1433}
1434
1435// Here we try to keep all subviews adjusted in as natural a manner as possible, given the constraints.
1436// The main idea is to always keep the RBSplitView completely covered by dividers and subviews, have at
1437// least one expanded subview, and never make a subview smaller than its minimum dimension, or larger
1438// than its maximum dimension.
1439// We try to account for most unusual situations but this may fail under some circumstances. YMMV.
1440- (void)RB___adjustSubviewsExcepting:(RBSplitSubview*)excepting {
1441 mustAdjust = NO;
1442 NSArray* subviews = [self RB___subviews];
1443 NSUInteger subcount = [subviews count];
1444 if (subcount<1) {
1445 return;
1446 }
1447 NSRect bounds = [self bounds];
1448// Never adjust if the splitview itself is collapsed.
1449 if ((bounds.size.width<1.0)||(bounds.size.height<1.0)) {
1450 return;
1451 }
1452// Prevents adjustSubviews being called recursively, which unfortunately may happen otherwise.
1453 if (isAdjusting) {
1454 return;
1455 }
1456 isAdjusting = YES;
1457// Tell the delegate we're about to adjust subviews.
1458 if ([delegate respondsToSelector:@selector(willAdjustSubviews:)]) {
1459 [delegate willAdjustSubviews:self];
1460 bounds = [self bounds];
1461 }
1462 NSUInteger divcount = subcount-1;
1463 if (divcount<1) {
1464// No dividers at all.
1465 if (dividers) {
1466 free(dividers);
1467 dividers = NULL;
1468 }
1469 } else {
1470// Try to allocate or resize if we already have a dividers array.
1471 NSUInteger divsiz = sizeof(NSRect)*divcount;
1472 dividers = (NSRect*)(dividers?reallocf(dividers,divsiz):malloc(divsiz));
1473 if (!dividers) {
1474 return;
1475 }
1476 }
1477// This C array of subviewCaches is used to cache the subview information.
1478 subviewCache* caches = (subviewCache*)malloc(sizeof(subviewCache)*subcount);
1479 double realsize = 0.0;
1480 double expsize = 0.0;
1481 CGFloat newsize = 0.0;
1482 CGFloat effsize = 0.0;
1483 CGFloat limit;
1484 subviewCache* curr;
1485 NSUInteger i;
1486 BOOL ishor = [self isHorizontal];
1487 CGFloat divt = [self dividerThickness];
1488// First we loop over subviews and cache their information.
1489 for (i=0;i<subcount;i++) {
1490 curr = &caches[i];
1491 [[subviews objectAtIndex:i] RB___copyIntoCache:curr];
1492 }
1493// This is a counter to limit the outer loop to three iterations (six if excepting is non-nil).
1494 NSInteger sanity = excepting?-3:0;
1495 while (sanity++<3) {
1496// We try to accomodate the exception for the first group of loops, turn it off for the second.
1497 if (sanity==1) {
1498 excepting = nil;
1499 }
1500// newsize is the available space for actual subviews (so dividers don't count). It will be an integer.
1501// Same as calling [self RB___dimensionWithoutDividers].
1502 NSUInteger smallest = 0;
1503 CGFloat smalldim = -1.0;
1504 BOOL haveexp = NO;
1505// Loop over subviews and sum the expanded dimensions into expsize, including fractions.
1506// Also find the collapsed subview with the smallest minimum dimension.
1507 for (i=0;i<subcount;i++) {
1508 curr = &caches[i];
1509 curr->constrain = NO;
1510 if (curr->size>0.0) {
1511 expsize += curr->size;
1512 if (!isInScrollView) {
1513// ignore fractions if we're in a NSScrollView, however.
1514 expsize += curr->fraction;
1515 }
1516 haveexp = YES;
1517 } else {
1518 limit = [curr->sub minDimension];
1519 if (smalldim>limit) {
1520 smalldim = limit;
1521 smallest = i;
1522 }
1523 }
1524 }
1525// haveexp should be YES at this point. If not, all subviews were collapsed; can't have that, so we
1526// expand the smallest subview (or the first, if all have the same minimum).
1527 curr = &caches[smallest];
1528 if (!haveexp) {
1529 curr->size = [curr->sub minDimension];
1530 curr->fraction = 0.0;
1531 expsize += curr->size;
1532 }
1533 if (isInScrollView) {
1534// If we're inside an NSScrollView, we just grow the view to accommodate the subviews, instead of
1535// the other way around.
1536 DIM(bounds.size) = expsize;
1537 break;
1538 } else {
1539// If the total dimension of all expanded subviews is less than 1.0 we set the dimension of the smallest
1540// subview (which we're sure is expanded at this point) to the available space.
1541 newsize = DIM(bounds.size)-divcount*divt;
1542 if (expsize<1.0) {
1543 curr->size = newsize;
1544 curr->fraction = 0.0;
1545 expsize = newsize;
1546 }
1547// Loop over the subviews and check if they're within the limits after scaling. We also recalculate the
1548// exposed size and repeat until no more subviews hit the constraints during that loop.
1549 BOOL constrained;
1550 effsize = newsize;// we're caching newsize here, this is an integer.
1551 do {
1552// scale is the scalefactor by which all views should be scaled - assuming none have constraints.
1553// It's a double to (hopefully) keep rounding errors small enough for all practical purposes.
1554 double scale = newsize/expsize;
1555 constrained = NO;
1556 realsize = 0.0;
1557 expsize = 0.0;
1558 for (i=0;i<subcount;i++) {
1559// Loop over the cached subview info.
1560 curr = &caches[i];
1561 if (curr->size>0.0) {
1562// Check non-collapsed subviews only.
1563 if (!curr->constrain) {
1564// Check non-constrained subviews only; calculate the proposed new size.
1565 CGFloat cursize = (curr->size+curr->fraction)*scale;
1566// Check if we hit a limit. limit will contain either the max or min dimension, whichever was hit.
1567 if (([curr->sub RB___animationData:NO resize:NO]&&((limit = curr->size)>=0.0))||
1568 ((curr->sub==excepting)&&((limit = [curr->sub dimension])>0.0))||
1569 (cursize<(limit = [curr->sub minDimension]))||
1570 (cursize>(limit = [curr->sub maxDimension]))) {
1571// If we hit a limit, we mark the view and set to repeat the loop; non-constrained subviews will
1572// have to be recalculated.
1573 curr->constrain = constrained = YES;
1574// We set the new size to the limit we hit, and subtract it from the total size to be subdivided.
1575 cursize = limit;
1576 curr->fraction = 0.0;
1577 newsize -= cursize;
1578 } else {
1579// If we didn't hit a limit, we round the size to the nearest integer and recalculate the fraction.
1580 double rem = fmod(cursize,1.0);
1581 cursize -= rem;
1582 if (rem>0.5) {
1583 ++cursize;
1584 --rem;
1585 }
1586 expsize += cursize;
1587 curr->fraction = rem;
1588 }
1589// We store the new size in the cache.
1590 curr->size = cursize;
1591 }
1592// And add the full size with fraction to the actual sum of all expanded subviews.
1593 realsize += curr->size+curr->fraction;
1594 }
1595 }
1596// At this point, newsize will be the sum of the new dimensions of non-constrained views.
1597// expsize will be the sum of the recalculated dimensions of the same views, if any.
1598// We repeat the loop if any view has been recently constrained, and if there are any
1599// unconstrained views left.
1600 } while (constrained&&(expsize>0.0));
1601// At this point, the difference between realsize and effsize should be less than 1 pixel.
1602// realsize is the total size of expanded subviews as recalculated above, and
1603// effsize is the value realsize should have.
1604 limit = realsize-effsize;
1605 if (limit>=1.0) {
1606// If realsize is larger than effsize by 1 pixel or more, we will need to collapse subviews to make room.
1607// This in turn might expand previously collapsed subviews. So, we'll try collapsing constrained subviews
1608// until we're back into range, and then recalculate everything from the beginning.
1609 for (i=0;i<subcount;i++) {
1610 curr = &caches[i];
1611 if (curr->constrain&&(curr->sub!=excepting)&&([curr->sub RB___animationData:NO resize:NO]==nil)&&[curr->sub canCollapse]) {
1612 realsize -= curr->size;
1613 if (realsize<1.0) {
1614 break;
1615 }
1616 curr->size = 0.0;
1617 if ((realsize-effsize)<1.0) {
1618 break;
1619 }
1620 }
1621 }
1622 } else if (limit<=-1.0) {
1623// If realsize is smaller than effsize by 1 pixel or more, we will need to expand subviews.
1624// This in turn might collapse previously expanded subviews. So, we'll try expanding collapsed subviews
1625// until we're back into range, and then recalculate everything from the beginning.
1626 for (i=0;i<subcount;i++) {
1627 curr = &caches[i];
1628 if (curr->size<=0.0) {
1629 curr->size = [curr->sub minDimension];
1630 curr->fraction = 0.0;
1631 realsize += curr->size;
1632 if ((realsize-effsize)>-1.0) {
1633 break;
1634 }
1635 }
1636 }
1637 } else {
1638// The difference is less than 1 pixel, meaning that in all probability our calculations are
1639// exact or off by at most one pixel after rounding, so we break the loop here.
1640 break;
1641 }
1642 }
1643// After passing through the outer loop a few times, the frames may still be wrong, but there's nothing
1644// else we can do about it. You probably should avoid this by some other means like setting a minimum
1645// or maximum size for the window, for instance, or leaving at least one unlimited subview.
1646 }
1647// newframe is used to reset all subview frames. Subviews always fill the entire RBSplitView along the
1648// current orientation.
1649 NSRect newframe = NSMakeRect(0.0,0.0,bounds.size.width,bounds.size.height);
1650// We now loop over the subviews yet again and set the definite frames, also recalculating the
1651// divider rectangles as we go along, and collapsing and expanding subviews whenever requested.
1652 RBSplitSubview* last = nil;
1653// And we make a note if there's any nested RBSplitView.
1654 NSInteger nested = NSNotFound;
1655// newsize = DIM(bounds.size)-divcount*divt;
1656 for (i=0;i<subcount;i++) {
1657 curr = &caches[i];
1658// If we have a nested split view store its index.
1659 if ((nested==NSNotFound)&&([curr->sub asSplitView]!=nil)) {
1660 nested = i;
1661 }
1662// Adjust the subview to the correct origin and resize it to fit into the "other" dimension.
1663 curr->rect.origin = newframe.origin;
1664 OTHER(curr->rect.size) = OTHER(newframe.size);
1665 DIM(curr->rect.size) = curr->size;
1666// Clear fractions for expanded subviews if requested.
1667 if ((curr->size>0.0)&&mustClearFractions) {
1668 curr->fraction = 0.0;
1669 }
1670// Ask the subview to do the actual moving/resizing etc. from the cache.
1671 [curr->sub RB___updateFromCache:curr withTotalDimension:effsize];
1672// Step to the next position and record the subview if it's not collapsed.
1673 DIM(newframe.origin) += curr->size;
1674 if (curr->size>0.0) {
1675 last = curr->sub;
1676 }
1677 if (i==divcount) {
1678// We're at the last subview, so we now check if the actual and calculated dimensions
1679// are the same.
1680 CGFloat remain = DIM(bounds.size)-DIM(newframe.origin);
1681 if (last&&(fabs(remain)>0.0)) {
1682// We'll resize the last expanded subview to whatever it takes to squeeze within the frame.
1683// Normally the change should be at most one pixel, but if too many subviews were constrained,
1684// this may be a large value, and the last subview may be resized beyond its constraints;
1685// there's nothing else to do at this point.
1686 newframe = [last frame];
1687 DIM(newframe.size) += remain;
1688 [last RB___setFrameSize:newframe.size withFraction:[last RB___fraction]-remain];
1689// And we loop back over the rightmost dividers (if any) to adjust their offsets.
1690 while ((i>0)&&(last!=[subviews objectAtIndex:i])) {
1691 DIM(dividers[--i].origin) += remain;
1692 }
1693 break;
1694 }
1695 } else {
1696// For any but the last subview, we just calculate the divider frame.
1697 DIM(newframe.size) = divt;
1698 if (dividers) { // test for NULL to satisfy the analyzer
1699 dividers[i] = newframe;
1700 }
1701 DIM(newframe.origin) += divt;
1702 }
1703 }
1704// We resize our frame at this point, if we're inside an NSScrollView.
1705 if (isInScrollView) {
1706 [super setFrameSize:bounds.size];
1707 }
1708// If there was at least one nested RBSplitView, we loop over the subviews and adjust those that need it.
1709 for (i=nested;i<subcount;i++) {
1710 curr = &caches[i];
1711 RBSplitView* sv = [curr->sub asSplitView];
1712 if ([sv mustAdjust]) {
1713 [sv adjustSubviews];
1714 }
1715 }
1716// Free the cache array.
1717 free(caches);
1718// Clear cursor rects.
1719 mustAdjust = NO;
1720 mustClearFractions = NO;
1721 [[self window] invalidateCursorRectsForView:self];
1722// Save the state for all subviews.
1723 if (!isDragging) {
1724 [self saveState:NO];
1725 }
1726// If we're a nested RBSplitView, also invalidate cursorRects for the superview.
1727 RBSplitView* sv = [self couplingSplitView];
1728 if (sv) {
1729 [[self window] invalidateCursorRectsForView:sv];
1730 }
1731 isAdjusting = NO;
1732// Tell the delegate we're finished.
1733 if ([delegate respondsToSelector:@selector(didAdjustSubviews:)]) {
1734 [delegate didAdjustSubviews:self];
1735 }
1736}
1737
1738@end
1739
return self
unsigned count
return nil
float y
float x
#define OTHER(x)
#define DIM(x)
#define HYSTERESIS
#define WAYOUT
RBSVCursorType
Definition RBSplitView.h:13
@ RBSVHorizontalCursor
Definition RBSplitView.h:14
@ RBSVDragCursor
Definition RBSplitView.h:17
@ RBSVVerticalCursor
Definition RBSplitView.h:15
@ RBSVCursorTypeCount
Definition RBSplitView.h:18
@ RBSV2WayCursor
Definition RBSplitView.h:16
static CGFloat fMAX(CGFloat a, CGFloat b)
Definition RBSplitView.m:25
static NSCursor * cursors[RBSVCursorTypeCount]
Definition RBSplitView.m:18
static const unsigned char RBSplitView_Copyright[] __attribute__((used))
static CGFloat fMIN(CGFloat a, CGFloat b)
Definition RBSplitView.m:21
NSUInteger position()
CGFloat RB___collapse()
NSUInteger actDivider
void RB___setFrameSize:withFraction:(NSSize size,[withFraction] double value)
void RB___setFrame:withFraction:notify:(NSRect rect,[withFraction] double value,[notify] BOOL notify)
CGFloat changeDimensionBy:mayCollapse:move:(CGFloat increment,[mayCollapse] BOOL mayCollapse,[move] BOOL move)
void RB___setHidden:(BOOL flag)
NSString * identifier
RBSplitView * splitView()
NSRect * dividers
Definition RBSplitView.h:29
BOOL canSaveState
Definition RBSplitView.h:34
void removeStateUsingName:(NSString *name)
Definition RBSplitView.m:63
void RB___drawDividersIn:forDividerRect:thickness:(RBSplitView *masterView,[forDividerRect] NSRect rect,[thickness] CGFloat delta)
RBSplitView * asSplitView()
void resetCursorRects()
IBOutlet id delegate
Definition RBSplitView.h:25
void RB___adjustOutermostIfNeeded()
void adjustSubviews()
NSString * description()
NSString * autosaveName
Definition RBSplitView.h:26
void RB___addCursorRectsTo:forDividerRect:thickness:(RBSplitView *masterView,[forDividerRect] NSRect rect,[thickness] CGFloat delta)
RBSplitView * coupledSplitView()
NSColor * background
Definition RBSplitView.h:27
NSString * stringWithSavedState()
void drawDivider:inRect:betweenView:andView:(NSImage *anImage,[inRect] NSRect rect,[betweenView] RBSplitSubview *leading,[andView] RBSplitSubview *trailing)
BOOL autoresizesSubviews()
NSArray * arrayWithStates()
void RB___trackMouseEvent:from:withBase:inDivider:(NSEvent *theEvent,[from] NSPoint where,[withBase] NSPoint base,[inDivider] NSUInteger indx)
NSUInteger RB___dividerHitBy:relativeToView:thickness:(NSPoint point,[relativeToView] RBSplitView *view,[thickness] CGFloat delta)
BOOL mustAdjust
Definition RBSplitView.h:31
BOOL isAdjusting
Definition RBSplitView.h:36
CGFloat dividerThickness
Definition RBSplitView.h:30
BOOL mouseDownCanMoveWindow()
BOOL needsDisplay()
void setAutosaveName:recursively:(NSString *aString,[recursively] BOOL flag)
Definition RBSplitView.m:87
NSImage * divider
Definition RBSplitView.h:28
void awakeFromNib()
NSCursor * cursor:(RBSVCursorType type)
Definition RBSplitView.m:33
RBSplitView * couplingSplitView()
void viewDidMoveToSuperview()
NSRect RB___dividerRect:relativeToView:(NSUInteger indx,[relativeToView] RBSplitView *view)
BOOL isInScrollView
Definition RBSplitView.h:38
void setMustAdjust()
BOOL isVertical()
BOOL isDragging
Definition RBSplitView.h:37
BOOL saveState:(BOOL recurse)
BOOL isHorizontal
Definition RBSplitView.h:33
voidpf void uLong size
Definition ioapi.h:134
voidpf uLong int origin
Definition ioapi.h:140
voidpf uLong offset
Definition ioapi.h:140