diff --git a/AppKit/CPSplitView.j b/AppKit/CPSplitView.j index 15201a59a..04c66b51c 100644 --- a/AppKit/CPSplitView.j +++ b/AppKit/CPSplitView.j @@ -30,6 +30,33 @@ @class CPUserDefaults @global CPApp +@protocol CPSplitViewDelegate + +@optional +- (BOOL)splitView:(CPSplitView)splitView canCollapseSubview:(CPView)subview; +- (BOOL)splitView:(CPSplitView)splitView shouldAdjustSizeOfSubview:(CPView)subview; +- (BOOL)splitView:(CPSplitView)splitView shouldCollapseSubview:(CPView)subview forDoubleClickOnDividerAtIndex:(CPInteger)dividerIndex; +- (CGRect)splitView:(CPSplitView)splitView additionalEffectiveRectOfDividerAtIndex:(CPInteger)dividerIndex; +- (CGRect)splitView:(CPSplitView)splitView effectiveRect:(CGRect)proposedEffectiveRect forDrawnRect:(CGRect)drawnRect ofDividerAtIndex:(CPInteger)dividerIndex; +- (float)splitView:(CPSplitView)splitView constrainMaxCoordinate:(float)proposedMax ofSubviewAt:(CPInteger)dividerIndex; +- (float)splitView:(CPSplitView)splitView constrainMinCoordinate:(float)proposedMin ofSubviewAt:(CPInteger)dividerIndex; +- (float)splitView:(CPSplitView)splitView constrainSplitPosition:(float)proposedPosition ofSubviewAt:(CPInteger)dividerIndex; +- (void)splitView:(CPSplitView)splitView resizeSubviewsWithOldSize:(CGSize)oldSize; +- (void)splitViewDidResizeSubviews:(CPNotification)aNotification; +- (void)splitViewWillResizeSubviews:(CPNotification)aNotification; + +@end + +var CPSplitViewDelegate_splitView_canCollapseSubview_ = 1 << 0, + CPSplitViewDelegate_splitView_shouldAdjustSizeOfSubview_ = 1 << 1, + CPSplitViewDelegate_splitView_shouldCollapseSubview_forDoubleClickOnDividerAtIndex_ = 1 << 2, + CPSplitViewDelegate_splitView_additionalEffectiveRectOfDividerAtIndex_ = 1 << 3, + CPSplitViewDelegate_splitView_effectiveRect_forDrawnRect_ofDividerAtIndex_ = 1 << 4, + CPSplitViewDelegate_splitView_constrainMaxCoordinate_ofSubviewAt_ = 1 << 5, + CPSplitViewDelegate_splitView_constrainMinCoordinate_ofSubviewAt_ = 1 << 6, + CPSplitViewDelegate_splitView_constrainSplitPosition_ofSubviewAt_ = 1 << 7, + CPSplitViewDelegate_splitView_resizeSubviewsWithOldSize_ = 1 << 8; + #define SPLIT_VIEW_MAYBE_POST_WILL_RESIZE() \ if ((_suppressResizeNotificationsMask & DidPostWillResizeNotification) === 0) \ { \ @@ -73,29 +100,31 @@ var ShouldSuppressResizeNotifications = 1, @implementation CPSplitView : CPView { - id _delegate; - BOOL _isVertical; - BOOL _isPaneSplitter; + id _delegate; + BOOL _isVertical; + BOOL _isPaneSplitter; - int _currentDivider; - float _initialOffset; - CPDictionary _preCollapsePositions; + int _currentDivider; + float _initialOffset; + CPDictionary _preCollapsePositions; - CPString _originComponent; - CPString _sizeComponent; + CPString _originComponent; + CPString _sizeComponent; - CPArray _DOMDividerElements; - CPString _dividerImagePath; - int _drawingDivider; + CPArray _DOMDividerElements; + CPString _dividerImagePath; + int _drawingDivider; - CPString _autosaveName; - BOOL _shouldAutosave; - CGSize _shouldRestoreFromAutosaveUnlessFrameSize; + CPString _autosaveName; + BOOL _shouldAutosave; + CGSize _shouldRestoreFromAutosaveUnlessFrameSize; - BOOL _needsResizeSubviews; - int _suppressResizeNotificationsMask; + BOOL _needsResizeSubviews; + int _suppressResizeNotificationsMask; - CPArray _buttonBars; + CPArray _buttonBars; + + unsigned _implementedDelegateMethods; } + (CPString)defaultThemeClass @@ -406,11 +435,8 @@ var ShouldSuppressResizeNotifications = 1, buttonBarRect.origin = [self convertPoint:buttonBarRect.origin fromView:buttonBar]; } - if ([_delegate respondsToSelector:@selector(splitView:effectiveRect:forDrawnRect:ofDividerAtIndex:)]) - effectiveRect = [_delegate splitView:self effectiveRect:effectiveRect forDrawnRect:effectiveRect ofDividerAtIndex:anIndex]; - - if ([_delegate respondsToSelector:@selector(splitView:additionalEffectiveRectOfDividerAtIndex:)]) - additionalRect = [_delegate splitView:self additionalEffectiveRectOfDividerAtIndex:anIndex]; + effectiveRect = [self _sendDelegateSplitViewEffectiveRect:effectiveRect forDrawnRect:effectiveRect ofDividerAtIndex:anIndex]; + additionalRect = [self _sendDelegateSplitViewAdditionalEffectiveRectOfDividerAtIndex:anIndex]; return CGRectContainsPoint(effectiveRect, aPoint) || (additionalRect && CGRectContainsPoint(additionalRect, aPoint)) || @@ -472,21 +498,21 @@ var ShouldSuppressResizeNotifications = 1, if ([self cursorAtPoint:point hitDividerAtIndex:i]) { if ([anEvent clickCount] == 2 && - [_delegate respondsToSelector:@selector(splitView:canCollapseSubview:)] && - [_delegate respondsToSelector:@selector(splitView:shouldCollapseSubview:forDoubleClickOnDividerAtIndex:)]) + [self _delegateRespondsToSplitViewCanCollapseSubview] && + [self _delegateRespondsToSplitViewshouldCollapseSubviewForDoubleClickOnDividerAtIndex]) { var minPosition = [self minPossiblePositionOfDividerAtIndex:i], maxPosition = [self maxPossiblePositionOfDividerAtIndex:i], preCollapsePosition = [_preCollapsePositions objectForKey:"" + i] || 0; - if ([_delegate splitView:self canCollapseSubview:_subviews[i]] && [_delegate splitView:self shouldCollapseSubview:_subviews[i] forDoubleClickOnDividerAtIndex:i]) + if ([self _sendDelegateSplitViewCanCollapseSubview:_subviews[i]] && [self _sendDelegateSplitViewShouldCollapseSubview:_subviews[i] forDoubleClickOnDividerAtIndex:i]) { if ([self isSubviewCollapsed:_subviews[i]]) [self setPosition:preCollapsePosition ? preCollapsePosition : (minPosition + (maxPosition - minPosition) / 2) ofDividerAtIndex:i]; else [self setPosition:minPosition ofDividerAtIndex:i]; } - else if ([_delegate splitView:self canCollapseSubview:_subviews[i + 1]] && [_delegate splitView:self shouldCollapseSubview:_subviews[i + 1] forDoubleClickOnDividerAtIndex:i]) + else if ([self _sendDelegateSplitViewCanCollapseSubview:_subviews[i + 1]] && [self _sendDelegateSplitViewShouldCollapseSubview:_subviews[i + 1] forDoubleClickOnDividerAtIndex:i]) { if ([self isSubviewCollapsed:_subviews[i + 1]]) [self setPosition:preCollapsePosition ? preCollapsePosition : (minPosition + (maxPosition - minPosition) / 2) ofDividerAtIndex:i]; @@ -580,12 +606,8 @@ var ShouldSuppressResizeNotifications = 1, if (sizeA === 0) canGrow = YES; // Subview is collapsed. - else if (!canShrink && - [_delegate respondsToSelector:@selector(splitView:canCollapseSubview:)] && - [_delegate splitView:self canCollapseSubview:_subviews[i]]) - { + else if (!canShrink && [self _sendDelegateSplitViewCanCollapseSubview:_subviews[i]]) canShrink = YES; // Subview is collapsible. - } if (sizeB === 0) { @@ -594,9 +616,7 @@ var ShouldSuppressResizeNotifications = 1, // It's safe to assume it can always be uncollapsed. canShrink = YES; } - else if (!canGrow && - [_delegate respondsToSelector:@selector(splitView:canCollapseSubview:)] && - [_delegate splitView:self canCollapseSubview:_subviews[i + 1]]) + else if (!canGrow && [self _sendDelegateSplitViewCanCollapseSubview:_subviews[i + 1]]) { canGrow = YES; // Right/lower subview is collapsible. } @@ -657,53 +677,38 @@ var ShouldSuppressResizeNotifications = 1, - (int)_realPositionForPosition:(float)position ofDividerAtIndex:(int)dividerIndex { // not sure where this should override other positions? - if ([_delegate respondsToSelector:@selector(splitView:constrainSplitPosition:ofSubviewAt:)]) - { - var proposedPosition = [_delegate splitView:self constrainSplitPosition:position ofSubviewAt:dividerIndex]; + var proposedPosition = [self _sendDelegateSplitViewConstrainSplitPosition:position ofSubviewAt:dividerIndex]; - // Silently ignore bad positions which could result from odd delegate responses. We don't want these - // bad results to go into the system and cause havoc with frame sizes as the split view tries to resize - // its subviews. - if (_IS_NUMERIC(proposedPosition)) - position = proposedPosition; - } + // Silently ignore bad positions which could result from odd delegate responses. We don't want these + // bad results to go into the system and cause havoc with frame sizes as the split view tries to resize + // its subviews. + if (_IS_NUMERIC(proposedPosition)) + position = proposedPosition; var proposedMax = [self maxPossiblePositionOfDividerAtIndex:dividerIndex], proposedMin = [self minPossiblePositionOfDividerAtIndex:dividerIndex], actualMax = proposedMax, - actualMin = proposedMin; + actualMin = proposedMin, + proposedActualMin = [self _sendDelegateSplitViewConstrainMinCoordinate:proposedMin ofSubviewAt:dividerIndex], + proposedActualMax = [self _sendDelegateSplitViewConstrainMaxCoordinate:proposedMax ofSubviewAt:dividerIndex]; - if ([_delegate respondsToSelector:@selector(splitView:constrainMinCoordinate:ofSubviewAt:)]) - { - var proposedActualMin = [_delegate splitView:self constrainMinCoordinate:proposedMin ofSubviewAt:dividerIndex]; + if (_IS_NUMERIC(proposedActualMin)) + actualMin = proposedActualMin; - if (_IS_NUMERIC(proposedActualMin)) - actualMin = proposedActualMin; - } - - if ([_delegate respondsToSelector:@selector(splitView:constrainMaxCoordinate:ofSubviewAt:)]) - { - var proposedActualMax = [_delegate splitView:self constrainMaxCoordinate:proposedMax ofSubviewAt:dividerIndex]; - - if (_IS_NUMERIC(proposedActualMax)) - actualMax = proposedActualMax; - } + if (_IS_NUMERIC(proposedActualMax)) + actualMax = proposedActualMax; var viewA = _subviews[dividerIndex], viewB = _subviews[dividerIndex + 1], realPosition = MAX(MIN(position, actualMax), actualMin); // Is this position past the halfway point to collapse? - if (position < proposedMin + (actualMin - proposedMin) / 2) - if ([_delegate respondsToSelector:@selector(splitView:canCollapseSubview:)]) - if ([_delegate splitView:self canCollapseSubview:viewA]) - realPosition = proposedMin; + if ((position < proposedMin + (actualMin - proposedMin) / 2) && [self _sendDelegateSplitViewCanCollapseSubview:viewA]) + realPosition = proposedMin; // We can also collapse to the right. - if (position > proposedMax - (proposedMax - actualMax) / 2) - if ([_delegate respondsToSelector:@selector(splitView:canCollapseSubview:)]) - if ([_delegate splitView:self canCollapseSubview:viewB]) - realPosition = proposedMax; + if ((position > proposedMax - (proposedMax - actualMax) / 2) && [self _sendDelegateSplitViewCanCollapseSubview:viewB]) + realPosition = proposedMax; return realPosition; } @@ -787,9 +792,9 @@ var ShouldSuppressResizeNotifications = 1, - (void)resizeSubviewsWithOldSize:(CGSize)oldSize { - if ([_delegate respondsToSelector:@selector(splitView:resizeSubviewsWithOldSize:)]) + if ([self _delegateRespondsToSplitViewResizeSubviewsWithOldSize]) { - [_delegate splitView:self resizeSubviewsWithOldSize:oldSize]; + [self _sendDelegateSplitViewResizeSubviewsWithOldSize:oldSize]; return; } @@ -815,8 +820,7 @@ var ShouldSuppressResizeNotifications = 1, oldFlexibleSpace = 0, totalSizablePanes = 0, isSizableMap = {}, - viewSizes = [], - delegateRespondsToShouldAdjust = [_delegate respondsToSelector:@selector(splitView:shouldAdjustSizeOfSubview:)]; + viewSizes = []; // What we want to do is to preserve non resizable sizes first, and then to preserve the ratio of size to available // non fixed space for every other subview. E.g. assume fixed space was 20 pixels initially, view 1 was 20 and @@ -832,7 +836,7 @@ var ShouldSuppressResizeNotifications = 1, for (index = 0; index < count; ++index) { var view = _subviews[index], - isSizable = !delegateRespondsToShouldAdjust || [_delegate splitView:self shouldAdjustSizeOfSubview:view], + isSizable = [self _sendDelegateSplitViewShouldAdjustSizeOfSubview:view], size = [view frame].size[_sizeComponent]; isSizableMap[index] = isSizable; @@ -958,25 +962,59 @@ The sum of the views and the sum of the dividers should be equal to the size of @param delegate - The delegate of the splitview. */ -- (void)setDelegate:(id)delegate +- (void)setDelegate:(id )aDelegate { + if (_delegate === aDelegate) + return; + if ([_delegate respondsToSelector:@selector(splitViewDidResizeSubviews:)]) [[CPNotificationCenter defaultCenter] removeObserver:_delegate name:CPSplitViewDidResizeSubviewsNotification object:self]; + if ([_delegate respondsToSelector:@selector(splitViewWillResizeSubviews:)]) [[CPNotificationCenter defaultCenter] removeObserver:_delegate name:CPSplitViewWillResizeSubviewsNotification object:self]; - _delegate = delegate; + _delegate = aDelegate; + _implementedDelegateMethods = 0; if ([_delegate respondsToSelector:@selector(splitViewDidResizeSubviews:)]) [[CPNotificationCenter defaultCenter] addObserver:_delegate selector:@selector(splitViewDidResizeSubviews:) name:CPSplitViewDidResizeSubviewsNotification object:self]; + if ([_delegate respondsToSelector:@selector(splitViewWillResizeSubviews:)]) [[CPNotificationCenter defaultCenter] addObserver:_delegate selector:@selector(splitViewWillResizeSubviews:) name:CPSplitViewWillResizeSubviewsNotification object:self]; + + if ([_delegate respondsToSelector:@selector(splitView:canCollapseSubview:)]) + _implementedDelegateMethods |= CPSplitViewDelegate_splitView_canCollapseSubview_; + + if ([_delegate respondsToSelector:@selector(splitView:shouldAdjustSizeOfSubview:)]) + _implementedDelegateMethods |= CPSplitViewDelegate_splitView_shouldAdjustSizeOfSubview_; + + if ([_delegate respondsToSelector:@selector(splitView:shouldCollapseSubview:forDoubleClickOnDividerAtIndex:)]) + _implementedDelegateMethods |= CPSplitViewDelegate_splitView_shouldCollapseSubview_forDoubleClickOnDividerAtIndex_; + + if ([_delegate respondsToSelector:@selector(splitView:additionalEffectiveRectOfDividerAtIndex:)]) + _implementedDelegateMethods |= CPSplitViewDelegate_splitView_additionalEffectiveRectOfDividerAtIndex_; + + if ([_delegate respondsToSelector:@selector(splitView:effectiveRect:forDrawnRect:ofDividerAtIndex:)]) + _implementedDelegateMethods |= CPSplitViewDelegate_splitView_effectiveRect_forDrawnRect_ofDividerAtIndex_; + + if ([_delegate respondsToSelector:@selector(splitView:constrainMaxCoordinate:ofSubviewAt:)]) + _implementedDelegateMethods |= CPSplitViewDelegate_splitView_constrainMaxCoordinate_ofSubviewAt_; + + if ([_delegate respondsToSelector:@selector(splitView:constrainMinCoordinate:ofSubviewAt:)]) + _implementedDelegateMethods |= CPSplitViewDelegate_splitView_constrainMinCoordinate_ofSubviewAt_; + + if ([_delegate respondsToSelector:@selector(splitView:constrainSplitPosition:ofSubviewAt:)]) + _implementedDelegateMethods |= CPSplitViewDelegate_splitView_constrainSplitPosition_ofSubviewAt_; + + if ([_delegate respondsToSelector:@selector(splitView:resizeSubviewsWithOldSize:)]) + _implementedDelegateMethods |= CPSplitViewDelegate_splitView_resizeSubviewsWithOldSize_; + } /*! @@ -1192,6 +1230,148 @@ The sum of the views and the sum of the dividers should be equal to the size of @end + +@implementation CPSplitView (CPSplitViewDelegate) + +/*! + @ignore + Return YES if the delegate implements splitView:resizeSubviewsWithOldSize: +*/ +- (BOOL)_delegateRespondsToSplitViewResizeSubviewsWithOldSize +{ + return _implementedDelegateMethods & CPSplitViewDelegate_splitView_resizeSubviewsWithOldSize_; +} + +/*! + @ignore + Return YES if the delegate implements splitView:canCollapseSubview: +*/ +- (BOOL)_delegateRespondsToSplitViewCanCollapseSubview +{ + return _implementedDelegateMethods & CPSplitViewDelegate_splitView_canCollapseSubview_; +} + +/*! + @ignore + Return YES if the delegate implements splitView:shouldCollapseSubview:forDoubleClickOnDividerAtIndex +*/ +- (BOOL)_delegateRespondsToSplitViewshouldCollapseSubviewForDoubleClickOnDividerAtIndex +{ + return _implementedDelegateMethods & CPSplitViewDelegate_splitView_shouldCollapseSubview_forDoubleClickOnDividerAtIndex_; +} + + +/*! + @ignore + Call the delegate splitView:canCollapseSubview: +*/ +- (BOOL)_sendDelegateSplitViewCanCollapseSubview:(CPView)aView +{ + if (!(_implementedDelegateMethods & CPSplitViewDelegate_splitView_canCollapseSubview_)) + return NO; + + return [_delegate splitView:self canCollapseSubview:aView]; +} + +/*! + @ignore + Call the delegate splitView:shouldAdjustSizeOfSubview: +*/ +- (BOOL)_sendDelegateSplitViewShouldAdjustSizeOfSubview:(CPView)aView +{ + if (!(_implementedDelegateMethods & CPSplitViewDelegate_splitView_shouldAdjustSizeOfSubview_)) + return YES; + + return [_delegate splitView:self shouldAdjustSizeOfSubview:aView]; +} + +/*! + @ignore + Call the delegate splitView:shouldCollapseSubview:forDoubleClickOnDividerAtIndex: +*/ +- (BOOL)_sendDelegateSplitViewShouldCollapseSubview:(CPView)aView forDoubleClickOnDividerAtIndex:(int)anIndex +{ + if (!(_implementedDelegateMethods & CPSplitViewDelegate_splitView_shouldCollapseSubview_forDoubleClickOnDividerAtIndex_)) + return NO; + + return [_delegate splitView:self shouldCollapseSubview:aView forDoubleClickOnDividerAtIndex:anIndex]; +} + +/*! + @ignore + Call the delegate splitView:additionalEffectiveRectOfDividerAtIndex: +*/ +- (CGRect)_sendDelegateSplitViewAdditionalEffectiveRectOfDividerAtIndex:(int)anIndex +{ + if (!(_implementedDelegateMethods & CPSplitViewDelegate_splitView_additionalEffectiveRectOfDividerAtIndex_)) + return nil; + + return [_delegate splitView:self additionalEffectiveRectOfDividerAtIndex:anIndex]; +} + +/*! + @ignore + Call the delegate splitView:effectiveRect:forDrawnRect:ofDividerAtIndex: +*/ +- (CGRect)_sendDelegateSplitViewEffectiveRect:(CGRect)proposedEffectiveRect forDrawnRect:(CGRect)drawnRect ofDividerAtIndex:(CPInteger)dividerIndex +{ + if (!(_implementedDelegateMethods & CPSplitViewDelegate_splitView_effectiveRect_forDrawnRect_ofDividerAtIndex_)) + return proposedEffectiveRect; + + return [_delegate splitView:self effectiveRect:proposedEffectiveRect forDrawnRect:drawnRect ofDividerAtIndex:dividerIndex]; +} + +/*! + @ignore + Call the delegate splitView:constrainMaxCoordinate:ofSubviewAt: +*/ +- (float)_sendDelegateSplitViewConstrainMaxCoordinate:(float)proposedMax ofSubviewAt:(CPInteger)dividerIndex +{ + if (!(_implementedDelegateMethods & CPSplitViewDelegate_splitView_constrainMaxCoordinate_ofSubviewAt_)) + return nil; + + return [_delegate splitView:self constrainMaxCoordinate:proposedMax ofSubviewAt:dividerIndex]; +} + +/*! + @ignore + Call the delegate splitView:constrainMinCoordinate:ofSubviewAt: +*/ +- (float)_sendDelegateSplitViewConstrainMinCoordinate:(float)proposedMin ofSubviewAt:(CPInteger)dividerIndex +{ + if (!(_implementedDelegateMethods & CPSplitViewDelegate_splitView_constrainMinCoordinate_ofSubviewAt_)) + return nil; + + return [_delegate splitView:self constrainMinCoordinate:proposedMin ofSubviewAt:dividerIndex]; +} + +/*! + @ignore + Call the delegate splitView:constrainSplitPosition:ofSubviewAt: +*/ +- (float)_sendDelegateSplitViewConstrainSplitPosition:(float)proposedMax ofSubviewAt:(CPInteger)dividerIndex +{ + if (!(_implementedDelegateMethods & CPSplitViewDelegate_splitView_constrainSplitPosition_ofSubviewAt_)) + return nil; + + return [_delegate splitView:self constrainSplitPosition:proposedMax ofSubviewAt:dividerIndex]; +} + +/*! + @ignore + Call the delegate splitView:resizeSubviewsWithOldSize: +*/ +- (void)_sendDelegateSplitViewResizeSubviewsWithOldSize:(CGSize)oldSize +{ + if (!(_implementedDelegateMethods & CPSplitViewDelegate_splitView_resizeSubviewsWithOldSize_)) + return; + + [_delegate splitView:self resizeSubviewsWithOldSize:oldSize]; +} + +@end + + var CPSplitViewDelegateKey = "CPSplitViewDelegateKey", CPSplitViewIsVerticalKey = "CPSplitViewIsVerticalKey", CPSplitViewIsPaneSplitterKey = "CPSplitViewIsPaneSplitterKey",