Merge pull request #10 from gunyarakun/fix-invalid-return
[cocotron.git] / AppKit / NSSplitView.m
blob1add45557b41389b5121229d341a7d2172f299a3
1 /* Copyright (c) 2006-2007 Christopher J. W. Lloyd
2                  2009 Markus Hitter <mah@jump-ing.de>
4 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
10 #import <AppKit/NSSplitView.h>
11 #import <AppKit/NSColor.h>
12 #import <AppKit/NSGraphics.h>
13 #import <AppKit/NSEvent.h>
14 #import <AppKit/NSWindow.h>
15 #import <AppKit/NSGraphicsContext.h>
16 #import <AppKit/NSImage.h>
17 #import <AppKit/NSCursor.h>
18 #import <Foundation/NSKeyedArchiver.h>
19 #import <AppKit/NSRaise.h>
21 NSString * const NSSplitViewDidResizeSubviewsNotification = @"NSSplitViewDidResizeSubviewsNotification";
22 NSString * const NSSplitViewWillResizeSubviewsNotification = @"NSSplitViewWillResizeSubviewsNotification";
24 @implementation NSSplitView
26 -(void)encodeWithCoder:(NSCoder *)coder {
27    NSUnimplementedMethod();
30 -initWithCoder:(NSCoder *)coder {
31    [super initWithCoder:coder];
33    if([coder allowsKeyedCoding]){
34     NSKeyedUnarchiver *keyed=(NSKeyedUnarchiver *)coder;
35     
36     _isVertical=[keyed decodeBoolForKey:@"NSIsVertical"];
37 // The divider thickness in the nib may not be the same as ours
38     [self resizeSubviewsWithOldSize:[self bounds].size];
39    }
40    else {
41     [NSException raise:NSInvalidArgumentException format:@"-[%@ %s] is not implemented for coder %@",isa,sel_getName(_cmd),coder];
42    }
44    return self;
47 -(id)delegate {
48    return _delegate;
51 -(BOOL)isVertical {
52    return _isVertical;
55 -(void)_postNoteWillResize {
56     [[NSNotificationCenter defaultCenter] postNotificationName:NSSplitViewWillResizeSubviewsNotification object:self];
59 -(void)_postNoteDidResize {
60     [[NSNotificationCenter defaultCenter] postNotificationName:NSSplitViewDidResizeSubviewsNotification object:self];
63 -(void)setDelegate:(id)delegate {
64     if ([_delegate respondsToSelector:@selector(splitViewDidResizeSubviews:)])
65         [[NSNotificationCenter defaultCenter] removeObserver:_delegate name:NSSplitViewDidResizeSubviewsNotification object:self];
66     if ([_delegate respondsToSelector:@selector(splitViewWillResizeSubviews:)])
67         [[NSNotificationCenter defaultCenter] removeObserver:_delegate name:NSSplitViewWillResizeSubviewsNotification object:self];
68     
69    _delegate=delegate;
71    if ([_delegate respondsToSelector:@selector(splitViewDidResizeSubviews:)])
72        [[NSNotificationCenter defaultCenter] addObserver:_delegate
73                                                 selector:@selector(splitViewDidResizeSubviews:)
74                                                     name:NSSplitViewDidResizeSubviewsNotification
75                                                   object:self];
76    if ([_delegate respondsToSelector:@selector(splitViewWillResizeSubviews:)])
77        [[NSNotificationCenter defaultCenter] addObserver:_delegate
78                                                 selector:@selector(splitViewWillResizeSubviews:)
79                                                     name:NSSplitViewWillResizeSubviewsNotification
80                                                   object:self];
83 -(void)setVertical:(BOOL)flag {
84     if (_isVertical == flag) {
85         // Don't do unneccessary work
86         return;
87     }
88    _isVertical=flag;
89     
90     // Now get the split axis sorted
91    [self adjustSubviews];
94 -(BOOL)isFlipped {
95    return YES;
98 -(BOOL)isSubviewCollapsed:(NSView *)subview {
99     
100     /* From Apple's header comments:
101      * Collapsed subviews are hidden but retained by the split view.
102      * Collapsing of a subview will not change its bounds, but may set its frame
103      * to zero pixels high (in horizontal split views) or zero pixels wide (vertical).
104      */
105     return [subview isHidden];
108 -(void)setDividerStyle:(NSSplitViewDividerStyle)style {
109         _dividerStyle = style;
110         [self setNeedsDisplay: YES];
113 - (NSSplitViewDividerStyle)dividerStyle
115         return _dividerStyle;
118 /** adjust all the non-collapsed subviews so that they are equally spaced horizontally within the splitview */
119 - (void)_adjustSubviewWidths
121     // Set all the subview heights to the bounds height of the split view
122     float height = NSHeight([self bounds]);
124     int     i,count=[_subviews count];
126     float totalWidthBefore=0.;
127     
128     // The available width to the subviews
129     float totalWidthAfter=[self bounds].size.width-[self dividerThickness]*(count-1);
130     
131     for(i = 0; i < count; i++) {
132         NSView *subview = [_subviews objectAtIndex: i];
133         if ([self isSubviewCollapsed: subview] == NO) {
134             totalWidthBefore += NSWidth([subview frame]);
135         }
136     }
137    
138     float delta = totalWidthAfter / totalWidthBefore;
139     
140     NSRect  frame = [self bounds];
141     for(i = 0; i < count; i++){
142         NSView *subview = [_subviews objectAtIndex: i];
143         if ([self isSubviewCollapsed: subview] == NO) {
144             frame.size.width= NSWidth([subview frame]) * delta;
145             frame.size.width=floor(frame.size.width);
146             frame.size.height = height;
147             
148             NSSize oldSize = [subview frame].size;
149             
150             [subview setFrame:frame];
151             
152             frame.origin.x+= NSWidth(frame);
153             frame.origin.x+= [self dividerThickness];
154         }
155     }
158 - (void)_adjustSubviewHeights
160     // Set all the subview widths to the bounds width of the split view
161     float width = NSWidth([self bounds]);
163     int     i,count=[_subviews count];
165     // We've got to figure out how much the delta is between the old and new heights and multiply
166     // all the heights to the new delta to get them to fit (or something like that...) Apple says
167     // They resize proportionally
168     float totalHeightBefore=0.;
169     float totalHeightAfter=[self bounds].size.height-[self dividerThickness]*(count-1);
170     
171     for(i=0;i<count;i++) {
172         NSView *subview = [_subviews objectAtIndex: i];
173         if ([self isSubviewCollapsed: subview] == NO) {
174             NSRect subviewFrame = [subview frame];
175             totalHeightBefore += NSHeight(subviewFrame);
176         }
177     }
179     float delta = totalHeightAfter / totalHeightBefore;
180     
182     NSRect  frame=[self bounds];
183     for(i=0;i<count;i++){
184         NSView *subview = [_subviews objectAtIndex: i];
185         if ([self isSubviewCollapsed: subview] == NO) {
186             frame.size.height= NSHeight([subview frame]) * delta;
187             frame.size.height=floor(frame.size.height);
188             frame.size.width = width;
189             
190             NSSize oldSize = [subview frame].size;
191             
192             [subview setFrame:frame];
193             
194             frame.origin.y+= NSHeight(frame);
195             frame.origin.y+=[self dividerThickness];
196         }
197     }
201  * Apple says this method sets the frames of the split view's subviews so that they, plus the dividers,
202  * fill the split view. The default implementation of this method resizes all of the subviews
203  * proportionally so that the ratio of heights (in the horizontal split view case) or widths
204  * (in the vertical split view case) doesn't change, even though the absolute sizes of the
205  * subviews do change. This message should be sent to split views from which subviews have been
206  * added or removed, to reestablish the consistency of subview placement.
207  */
208 -(void)adjustSubviews {
210     if ([_subviews count] < 2) {
211         return;
212     }
213     
214    [self _postNoteWillResize];
216    if ([self isVertical]){
217         [self _adjustSubviewWidths];
218     }  else {
219         [self _adjustSubviewHeights];
220     }
221     [self setNeedsDisplay: YES];
222     [self _postNoteDidResize];
225 -(float)dividerThickness {
226         if (_dividerStyle == NSSplitViewDividerStyleThick) {
227                 return 10;
228         } else {
229                 return 5;
230         }
233 -(NSImage *)dimpleImage {
234    if([self isVertical])
235     return [NSImage imageNamed:@"NSSplitViewVDimple"];
236    else
237     return [NSImage imageNamed:@"NSSplitViewHDimple"];
240 -(void)drawDividerInRect:(NSRect)rect {
242         if (_dividerStyle != NSSplitViewDividerStylePaneSplitter) {
243                 // Fill in the view - pane splitter means just draw the dimple
244                 [[NSColor controlColor] setFill];
245                 NSRectFill(rect);
246         }
248         
249         NSImage *image=[self dimpleImage];
250         NSSize imageSize = [image size];
252         NSPoint point = rect.origin;
254         if([self isVertical]){
255                 point.x += floor((NSWidth(rect) - imageSize.width)/2);
256                 point.y += floor((NSHeight(rect) - imageSize.height)/2);
257    } else {
258                 point.x += floor((NSWidth(rect) - imageSize.width)/2);
259                 point.y += floor((NSHeight(rect) - imageSize.height)/2);
260    }
262    [image drawAtPoint: point fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0];
265 -(void)addSubview:(NSView *)view {
266    [super addSubview:view];
267    [self adjustSubviews];
270 -(void)resizeSubviewsWithOldSize:(NSSize)oldSize {
271    NSSize  size=[self bounds].size;
272    NSPoint origin=[self bounds].origin;
273    int     i,count=[_subviews count];
275    if(size.width<1) size.width=1;
276    if(size.height<1) size.height=1;
277    if(oldSize.width<1) oldSize.width=1;
278    if(oldSize.height<1) oldSize.height=1;
280    if([_delegate respondsToSelector:@selector(splitView:resizeSubviewsWithOldSize:)]) {
281        [_delegate splitView:self resizeSubviewsWithOldSize:oldSize];
282    } else {
284        // Apple docs say just call adjustSubviews
285        [self adjustSubviews];
286    }
289 -(NSRect)dividerRectAtIndex:(unsigned)index {
290    NSRect rect=[[_subviews objectAtIndex:index] frame];
292    if([self isVertical]){
293     rect.origin.x=rect.origin.x+rect.size.width;
294     rect.size.width=[self dividerThickness];
295    }
296    else {
297     rect.origin.y=rect.origin.y+rect.size.height;
298     rect.size.height=[self dividerThickness];
299    }
301    return rect;
304 -(void)drawRect:(NSRect)rect {
305    int i,count=[_subviews count];
307    for(i=0;i<count-1;i++){
308     if ([self dividerThickness] > 0)
309      [self drawDividerInRect:[self dividerRectAtIndex:i]];
310    }
313 -(unsigned)dividerIndexAtPoint:(NSPoint)point {
314    int i,count=[[self subviews] count];
316    for(i=0;i<count-1;i++){
317     NSRect rect=[self dividerRectAtIndex:i];
319     if(NSMouseInRect(point,rect,[self isFlipped]))
320      return i;
321    }
323    return NSNotFound;
326 static float constrainTo(float value,float min,float max){
327    if(value<min)
328     value=min;
329    if(value>max)
330     value=max;
331    return value;
334 -(void)mouseDown:(NSEvent *)event {
336     NSPoint  firstPoint=[self convertPoint:[event locationInWindow] fromView:nil];
337     unsigned divider=[self dividerIndexAtPoint:firstPoint];
339     if (divider == NSNotFound) {
340         return;
341     }
342     
343     NSEventType eventType;
345    [self _postNoteWillResize];
347    do{
348     NSAutoreleasePool *pool=[NSAutoreleasePool new];
349     NSPoint point;
351     event=[[self window] nextEventMatchingMask:NSLeftMouseUpMask|
352                           NSLeftMouseDraggedMask];
353     eventType=[event type];
355     point=[self convertPoint:[event locationInWindow] fromView:nil];
357        if([self isVertical]){
358            [self setPosition: point.x ofDividerAtIndex: divider];
359        } else {
360            [self setPosition: point.y ofDividerAtIndex: divider];
361        }
362        
363     [pool release];
364    }while(eventType!=NSLeftMouseUp);
366         if ([self dividerThickness] > 0)
367                 [[self window] invalidateCursorRectsForView:self];
369    [self _postNoteDidResize];
373 -(void)resetCursorRects {
374         
375    if ([self dividerThickness] <= 0)
376            return;
378         int       i,count=[_subviews count];
379    NSCursor *cursor;
381    if([self isVertical])
382     cursor=[NSCursor resizeLeftRightCursor];
383    else
384     cursor=[NSCursor resizeUpDownCursor];
386    for(i=0;i<count-1;i++){
387     NSRect rect=[self dividerRectAtIndex:i];
389 // FIX
390 // The cursor is activated one pixel past NSMaxY(rect) if we don't do
391 // this, not sure where the problem is. 
392     rect.origin.y-=1.;
394     [self addCursorRect:rect cursor:cursor];
395    }
398 - (float)minPossiblePositionOfDividerAtIndex:(int)index
400     NSUnimplementedMethod();
401     return 0;
404 - (float)maxPossiblePositionOfDividerAtIndex:(int)index
406     NSUnimplementedMethod();
407     return 0;
410 /** adjusts the subviews on either side of the divider while
411  * honoring the constraints imposed by the delegate (if any)
412  */
413 - (void)setPosition:(float)position ofDividerAtIndex:(int)index
415     NSAssert(index >= 0 && index < [[self subviews] count] - 1, @"divider index out of range");
416     
417     NSView *subview0 = [[self subviews] objectAtIndex: index];
418     NSView *subview1 = [[self subviews] objectAtIndex: index + 1];
420     BOOL subview0Expanded = [self isSubviewCollapsed: subview0] == NO;
421     BOOL subview1Expanded = [self isSubviewCollapsed: subview1] == NO;
422     
423     float    minPosition = 0;
424     float    maxPosition = 0;
425     
426     NSRect frame0 = NSZeroRect;
427     NSRect frame1 = NSZeroRect;
429     
430     if (subview0Expanded) {
431         frame0 = [subview0 frame];
432     }
433     
434     if (subview1Expanded) {
435         frame1 = [subview1 frame];
436     }
437     
438     // Determine the minimum position
439     if (subview0Expanded) {
440         if ([self isVertical]) {
441             minPosition = NSMinX(frame0);
442         } else {
443             minPosition = NSMinY(frame0);
444         }
445     } else {
446         NSAssert(subview1Expanded, @"both are collapsed??");
447         if ([self isVertical]) {
448             minPosition = NSMinX(frame1);
449         } else {
450             minPosition = NSMinY(frame1);
451         }
452     }
453     
454     // Determine the maximum position
455     if (subview1Expanded) {
456         if ([self isVertical]) {
457             maxPosition = NSMaxX(frame1);
458         } else {
459             maxPosition = NSMaxY(frame1);
460         }
461     } else {
462         NSAssert(subview0Expanded, @"both are collapsed??");
463         if ([self isVertical]) {
464             maxPosition = NSMaxX(frame0);
465         } else {
466             maxPosition = NSMaxY(frame0);
467         }
468     }
470     // Check in with the delegate and see if it wants to tweak the min and max
471     
472     if ([_delegate respondsToSelector: @selector(splitView:constrainMinCoordinate:ofSubviewAt:)] ||
473         [_delegate respondsToSelector: @selector(splitView:constrainMaxCoordinate:ofSubviewAt:)]) {
474         // Use the modern API
475         if ([_delegate respondsToSelector: @selector(splitView:constrainMinCoordinate:ofSubviewAt:)]) {
476             minPosition = [_delegate splitView: self constrainMinCoordinate: minPosition ofSubviewAt: index];
477         }
478         if ([_delegate respondsToSelector: @selector(splitView:constrainMaxCoordinate:ofSubviewAt:)]) {
479             maxPosition = [_delegate splitView: self constrainMaxCoordinate: maxPosition ofSubviewAt: index];
480         }
481     }
482     else if([_delegate respondsToSelector:@selector(splitView:constrainMinCoordinate:maxCoordinate:ofSubviewAt:)]) {
483         // Use the deprecated API
484         
485         [_delegate splitView:self constrainMinCoordinate:&minPosition maxCoordinate:&maxPosition ofSubviewAt: index];
486     }
487     
488     // And if it wants to constrain the divider position
489     BOOL delegateWantsTrackConstraining = [_delegate respondsToSelector:@selector(splitView:constrainSplitPosition:ofSubviewAt:)];
491     if(delegateWantsTrackConstraining) {
492         position = [_delegate splitView:self constrainSplitPosition: position ofSubviewAt: index];
493     }
494     
496     // OK we're ready to figure out where the divider can be positioned
497     NSRect  resize0=frame0;
498     NSRect  resize1=frame1;
499     
500     BOOL subviewsWereCollapsedOrExpanded = NO;
501     BOOL checkWithDelegateAboutCollapsingViews = [_delegate respondsToSelector:@selector(splitView:canCollapseSubview:)];
502     if([self isVertical]){
503         
504         float lastPosition = NSMaxX(resize0);
505         
506         float delta = floor(position - lastPosition);
507         
508         resize0.size.width += delta;
509         
510         resize1.size.width -= delta;
511         
512         if (checkWithDelegateAboutCollapsingViews) {
513             if (position < minPosition) {
514                 if ([_delegate splitView: self canCollapseSubview: subview0]) {
515                     [subview0 setHidden: YES];
516                     subviewsWereCollapsedOrExpanded = YES;
517                 }
518             } else if (position > maxPosition) {
519                 if ([_delegate splitView: self canCollapseSubview: subview1]) {
520                     [subview1 setHidden: YES];
521                     subviewsWereCollapsedOrExpanded = YES;
522                 }
523             }
524         }
525         
526         // But make sure collapsed views can reappear
527         if (position > minPosition && [subview0 isHidden]) {
528             [subview0 setHidden: NO];
529             subviewsWereCollapsedOrExpanded = YES;
530         } else if (position < maxPosition && [subview1 isHidden]) {
531             [subview1 setHidden: NO];
532             subviewsWereCollapsedOrExpanded = YES;
533         }
535         // Figure out the adjusted widths
536         resize0.size.width = constrainTo(NSWidth(resize0), minPosition, maxPosition);
537         resize1.size.width = constrainTo(NSWidth(resize1), minPosition, maxPosition);
538         resize1.origin.x = (NSMinX(frame1) + NSWidth(frame1)) - NSWidth(resize1);
539     }
540     else {
541         
542         float lastPosition = NSMaxY(resize0);
543         float delta = floor(position - lastPosition);
544         
545         resize0.size.height += delta;
547         resize1.size.height -= delta;
548         
549         if (checkWithDelegateAboutCollapsingViews) {
550             if (position < minPosition) {
551                 if ([_delegate splitView: self canCollapseSubview: subview0]) {
552                     [subview0 setHidden: YES];
553                     subviewsWereCollapsedOrExpanded = YES;
554                 }
555             } else if (position > maxPosition) {
556                 if ([_delegate splitView: self canCollapseSubview: subview1]) {
557                     [subview1 setHidden: YES];
558                     subviewsWereCollapsedOrExpanded = YES;
559                 }
560             }
561         }
562         
563         // But make sure collapsed views can reappear
564         if (position > minPosition && [subview0 isHidden]) {
565             [subview0 setHidden: NO];
566             subviewsWereCollapsedOrExpanded = YES;
567         } else if (position < maxPosition && [subview1 isHidden]) {
568             [subview1 setHidden: NO];
569             subviewsWereCollapsedOrExpanded = YES;
570         }
572         // Figure out the adjusted heights
573         resize0.size.height = constrainTo(NSHeight(resize0), minPosition, maxPosition);
574         resize1.size.height = constrainTo(NSHeight(resize1), minPosition, maxPosition);
575         resize1.origin.y = (NSMinY(frame1) + NSHeight(frame1)) - NSHeight(resize1);
576     }
578     if (subviewsWereCollapsedOrExpanded) {
579         // It doesn't really matter what happened with the divider because we need
580         // to get the views re-laid out - so fall back to adjustSubviews and bail
581         [self adjustSubviews];
582         return;
583     }
585     // Nothing special happened so just resize the subviews as expected
586     if ([subview0 isHidden] == NO) {
587         [subview0 setFrame: resize0];
588         // Tell the view to redisplay otherwise there are drawing artifacts
589         [subview0  setNeedsDisplay: YES];
590     }
591     
592     if ([subview1 isHidden] == NO) {
593         [subview1 setFrame: resize1];
594         // Tell the view to redisplay otherwise there are drawing artifacts
595         [subview1  setNeedsDisplay: YES];
596     }
597     
598     [self setNeedsDisplay:YES];
601 @end