23 #include "../Foundation/Foundation.h"
26 #define SPLIT_VIEW_MAYBE_POST_WILL_RESIZE() \
27 if ((_suppressResizeNotificationsMask & DidPostWillResizeNotification) === 0) \
29 [self _postNotificationWillResize]; \
30 _suppressResizeNotificationsMask |= DidPostWillResizeNotification; \
33 #define SPLIT_VIEW_MAYBE_POST_DID_RESIZE() \
34 if ((_suppressResizeNotificationsMask & ShouldSuppressResizeNotifications) !== 0) \
35 _suppressResizeNotificationsMask |= DidSuppressResizeNotification; \
37 [self _postNotificationDidResize];
39 #define SPLIT_VIEW_DID_SUPPRESS_RESIZE_NOTIFICATION() \
40 ((_suppressResizeNotificationsMask & DidSuppressResizeNotification) !== 0)
42 #define SPLIT_VIEW_SUPPRESS_RESIZE_NOTIFICATIONS(shouldSuppress) \
44 _suppressResizeNotificationsMask |= ShouldSuppressResizeNotifications; \
46 _suppressResizeNotificationsMask = 0;
89 CGSize _shouldRestoreFromAutosaveUnlessFrameSize;
91 BOOL _needsResizeSubviews;
92 int _suppressResizeNotificationsMask;
102 + (id)themeAttributes
105 forKeys:[@"divider-thickness", @"pane-divider-thickness", @"pane-divider-color"]];
122 - (id)initWithFrame:(CGRect)aFrame
124 if (
self = [super initWithFrame:aFrame])
126 _suppressResizeNotificationsMask = 0;
130 _DOMDividerElements = [];
133 _shouldAutosave = YES;
135 [
self _setVertical:YES];
145 - (float)dividerThickness
163 - (void)setVertical:(BOOL)shouldBeVertical
165 if (![
self _setVertical:shouldBeVertical])
172 [
self _postNotificationWillResize];
174 var eachSize = ROUND((
frame.size[_sizeComponent] - dividerThickness * (_subviews.length - 1)) / _subviews.length),
176 count = _subviews.length;
178 if ([
self isVertical])
180 for (; index < count; ++index)
181 [_subviews[index] setFrame:CGRectMake(ROUND((eachSize + dividerThickness) * index), 0, eachSize,
frame.size.height)];
185 for (; index < count; ++index)
186 [_subviews[index] setFrame:CGRectMake(0, ROUND((eachSize + dividerThickness) * index),
frame.size.width, eachSize)];
190 [
self _postNotificationDidResize];
194 - (BOOL)_setVertical:(BOOL)shouldBeVertical
196 var changed = (_isVertical != shouldBeVertical);
198 _isVertical = shouldBeVertical;
200 _originComponent = [
self isVertical] ?
"x" :
"y";
201 _sizeComponent = [
self isVertical] ?
"width" :
"height";
202 _dividerImagePath = [
self isVertical] ? [CPSplitViewVerticalImage filename] : [CPSplitViewHorizontalImage filename];
212 - (BOOL)isPaneSplitter
214 return _isPaneSplitter;
222 - (void)setIsPaneSplitter:(BOOL)shouldBePaneSplitter
224 if (_isPaneSplitter == shouldBePaneSplitter)
227 _isPaneSplitter = shouldBePaneSplitter;
229 if (_DOMDividerElements[_drawingDivider])
230 [
self _setupDOMDivider];
234 _needsResizeSubviews = YES;
240 _needsResizeSubviews = YES;
248 - (BOOL)isSubviewCollapsed:(
CPView)subview
250 return [subview
frame].size[_sizeComponent] < 1 ? YES : NO;
259 - (CGRect)rectOfDividerAtIndex:(
int)aDivider
261 var
frame = [_subviews[aDivider] frame],
262 rect = CGRectMakeZero();
264 rect.size = [
self frame].size;
266 rect.origin[_originComponent] =
frame.origin[_originComponent] +
frame.size[_sizeComponent];
277 - (CGRect)effectiveRectOfDividerAtIndex:(
int)aDivider
282 realRect.size[_sizeComponent] += padding * 2;
283 realRect.origin[_originComponent] -= padding;
288 - (void)drawRect:(CGRect)rect
290 var count = [_subviews count] - 1;
292 while ((count--) > 0)
294 _drawingDivider = count;
307 var dividerToRemove = _DOMDividerElements.pop();
311 CPDOMDisplayServerRemoveChild(_DOMElement, dividerToRemove);
314 _needsResizeSubviews = YES;
319 - (void)layoutSubviews
321 [
self _adjustSubviewsWithCalculatedSize]
328 - (void)drawDividerInRect:(CGRect)aRect
331 if (!_DOMDividerElements[_drawingDivider])
333 _DOMDividerElements[_drawingDivider] = document.createElement(
"div");
335 _DOMDividerElements[_drawingDivider].style.position =
"absolute";
336 _DOMDividerElements[_drawingDivider].style.backgroundRepeat =
"repeat";
338 CPDOMDisplayServerAppendChild(_DOMElement, _DOMDividerElements[_drawingDivider]);
341 [
self _setupDOMDivider];
342 CPDOMDisplayServerSetStyleLeftTop(_DOMDividerElements[_drawingDivider], NULL, _CGRectGetMinX(aRect), _CGRectGetMinY(aRect));
343 CPDOMDisplayServerSetStyleSize(_DOMDividerElements[_drawingDivider], _CGRectGetWidth(aRect), _CGRectGetHeight(aRect));
347 - (void)_setupDOMDivider
351 _DOMDividerElements[_drawingDivider].style.backgroundColor =
"";
352 _DOMDividerElements[_drawingDivider].style.backgroundImage =
"url('"+_dividerImagePath+"')";
356 _DOMDividerElements[_drawingDivider].style.backgroundColor = [[
self currentValueForThemeAttribute:@"pane-divider-color"] cssString];
357 _DOMDividerElements[_drawingDivider].style.backgroundImage =
"";
363 [
self _adjustSubviewsWithCalculatedSize];
366 - (void)_adjustSubviewsWithCalculatedSize
368 if (!_needsResizeSubviews)
371 _needsResizeSubviews = NO;
373 [
self resizeSubviewsWithOldSize:[
self _calculateSize]];
376 - (CGSize)_calculateSize
378 var subviews = [
self subviews],
379 count = subviews.length,
380 size = CGSizeMakeZero();
382 if ([
self isVertical])
384 size.width += [self dividerThickness] * (count - 1);
385 size.height = CGRectGetHeight([
self frame]);
389 size.width = CGRectGetWidth([
self frame]);
390 size.height += [self dividerThickness] * (count - 1);
394 size[_sizeComponent] += [subviews[count]
frame].size[_sizeComponent];
399 - (BOOL)cursorAtPoint:(CGPoint)aPoint hitDividerAtIndex:(
int)anIndex
401 var
frame = [_subviews[anIndex] frame],
402 startPosition = frame.origin[_originComponent] + frame.size[_sizeComponent],
404 buttonBar = _buttonBars[anIndex],
405 buttonBarRect = null,
406 additionalRect = null;
408 if (buttonBar != null)
410 buttonBarRect = [buttonBar resizeControlFrame];
414 if ([_delegate respondsToSelector:
@selector(splitView:effectiveRect:forDrawnRect:ofDividerAtIndex:)])
415 effectiveRect = [_delegate splitView:self effectiveRect:effectiveRect forDrawnRect:effectiveRect ofDividerAtIndex:anIndex];
417 if ([_delegate respondsToSelector:
@selector(splitView:additionalEffectiveRectOfDividerAtIndex:)])
418 additionalRect = [_delegate splitView:self additionalEffectiveRectOfDividerAtIndex:anIndex];
420 return CGRectContainsPoint(effectiveRect, aPoint) ||
421 (additionalRect && CGRectContainsPoint(additionalRect, aPoint)) ||
422 (buttonBarRect && CGRectContainsPoint(buttonBarRect, aPoint));
427 if ([
self isHidden] || ![
self hitTests] || !CGRectContainsPoint([
self frame], aPoint))
431 count = [_subviews count] - 1;
433 for (var i = 0; i < count; i++)
435 if ([
self cursorAtPoint:point hitDividerAtIndex:i])
448 var type = [anEvent
type];
453 _shouldAutosave = YES;
459 [
self _updateResizeCursor:anEvent];
468 count = [_subviews count] - 1;
472 for (var i = 0; i < count; i++)
474 var
frame = [_subviews[i] frame],
475 startPosition = frame.origin[_originComponent] + frame.size[_sizeComponent];
477 if ([
self cursorAtPoint:point hitDividerAtIndex:i])
479 if ([anEvent clickCount] == 2 &&
480 [_delegate respondsToSelector:
@selector(splitView:canCollapseSubview:)] &&
481 [_delegate respondsToSelector:@selector(splitView:shouldCollapseSubview:forDoubleClickOnDividerAtIndex:)])
485 preCollapsePosition = [_preCollapsePositions objectForKey:"" + i] || 0;
487 if ([_delegate splitView:
self canCollapseSubview:_subviews[i]] && [_delegate splitView:
self shouldCollapseSubview:_subviews[i] forDoubleClickOnDividerAtIndex:i])
489 if ([
self isSubviewCollapsed:_subviews[i]])
490 [
self setPosition:preCollapsePosition ? preCollapsePosition : (minPosition + (maxPosition - minPosition) / 2) ofDividerAtIndex:i];
494 else if ([_delegate splitView:
self canCollapseSubview:_subviews[i + 1]] && [_delegate splitView:
self shouldCollapseSubview:_subviews[i + 1] forDoubleClickOnDividerAtIndex:i])
496 if ([
self isSubviewCollapsed:_subviews[i + 1]])
497 [
self setPosition:preCollapsePosition ? preCollapsePosition : (minPosition + (maxPosition - minPosition) / 2) ofDividerAtIndex:i];
505 _initialOffset = startPosition - point[_originComponent];
508 _shouldAutosave = NO;
509 [
self _postNotificationWillResize];
524 [
self _updateResizeCursor:anEvent];
527 [CPApp setTarget:self selector:@selector(trackDivider:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
536 - (void)viewDidMoveToWindow
546 [
self _updateResizeCursor:anEvent];
552 [
self _updateResizeCursor:anEvent];
562 - (void)_updateResizeCursor:(
CPEvent)anEvent
566 if ([anEvent type] ===
CPLeftMouseUp && ![[
self window] acceptsMouseMovedEvents])
572 for (var i = 0, count = [_subviews count] - 1; i < count; i++)
575 if (_currentDivider === i || (_currentDivider ==
CPNotFound && [
self cursorAtPoint:point hitDividerAtIndex:i]))
577 var frameA = [_subviews[i] frame],
578 sizeA = frameA.size[_sizeComponent],
579 startPosition = frameA.origin[_originComponent] + sizeA,
580 frameB = [_subviews[i + 1]
frame],
581 sizeB = frameB.size[_sizeComponent],
582 canShrink = [self _realPositionForPosition:startPosition - 1 ofDividerAtIndex:i] < startPosition,
583 canGrow = [self _realPositionForPosition:startPosition + 1 ofDividerAtIndex:i] > startPosition,
584 cursor = [CPCursor arrowCursor];
588 else if (!canShrink &&
589 [_delegate respondsToSelector:
@selector(splitView:canCollapseSubview:)] &&
590 [_delegate splitView:
self canCollapseSubview:_subviews[i]])
603 [_delegate respondsToSelector:
@selector(splitView:canCollapseSubview:)] &&
604 [_delegate splitView:
self canCollapseSubview:_subviews[i + 1]])
609 if (_isVertical && canShrink && canGrow)
611 else if (_isVertical && canShrink)
613 else if (_isVertical && canGrow)
615 else if (canShrink && canGrow)
635 - (float)maxPossiblePositionOfDividerAtIndex:(
int)dividerIndex
637 var
frame = [_subviews[dividerIndex + 1] frame];
639 if (dividerIndex + 1 < [_subviews count] - 1)
640 return frame.origin[_originComponent] + frame.size[_sizeComponent] - [
self dividerThickness];
650 - (float)minPossiblePositionOfDividerAtIndex:(
int)dividerIndex
652 if (dividerIndex > 0)
654 var
frame = [_subviews[dividerIndex - 1] frame];
656 return frame.origin[_originComponent] + frame.size[_sizeComponent] + [
self dividerThickness];
662 - (int)_realPositionForPosition:(
float)position ofDividerAtIndex:(
int)dividerIndex
665 if ([_delegate respondsToSelector:
@selector(splitView:constrainSplitPosition:ofSubviewAt:)])
667 var proposedPosition = [_delegate splitView:self constrainSplitPosition:position ofSubviewAt:dividerIndex];
672 if (_IS_NUMERIC(proposedPosition))
673 position = proposedPosition;
676 var proposedMax = [
self maxPossiblePositionOfDividerAtIndex:dividerIndex],
677 proposedMin = [
self minPossiblePositionOfDividerAtIndex:dividerIndex],
678 actualMax = proposedMax,
679 actualMin = proposedMin;
681 if ([_delegate respondsToSelector:
@selector(splitView:constrainMinCoordinate:ofSubviewAt:)])
683 var proposedActualMin = [_delegate splitView:self constrainMinCoordinate:proposedMin ofSubviewAt:dividerIndex];
685 if (_IS_NUMERIC(proposedActualMin))
686 actualMin = proposedActualMin;
689 if ([_delegate respondsToSelector:
@selector(splitView:constrainMaxCoordinate:ofSubviewAt:)])
691 var proposedActualMax = [_delegate splitView:self constrainMaxCoordinate:proposedMax ofSubviewAt:dividerIndex];
693 if (_IS_NUMERIC(proposedActualMax))
694 actualMax = proposedActualMax;
697 var viewA = _subviews[dividerIndex],
698 viewB = _subviews[dividerIndex + 1],
699 realPosition = MAX(MIN(position, actualMax), actualMin);
702 if (position < proposedMin + (actualMin - proposedMin) / 2)
703 if ([_delegate respondsToSelector:
@selector(splitView:canCollapseSubview:)])
704 if ([_delegate splitView:
self canCollapseSubview:viewA])
705 realPosition = proposedMin;
708 if (position > proposedMax - (proposedMax - actualMax) / 2)
709 if ([_delegate respondsToSelector:
@selector(splitView:canCollapseSubview:)])
710 if ([_delegate splitView:
self canCollapseSubview:viewB])
711 realPosition = proposedMax;
721 - (void)setPosition:(
float)position ofDividerAtIndex:(
int)dividerIndex
725 _shouldRestoreFromAutosaveUnlessFrameSize = nil;
728 [
self _adjustSubviewsWithCalculatedSize];
730 var realPosition = [
self _realPositionForPosition:position ofDividerAtIndex:dividerIndex],
731 viewA = _subviews[dividerIndex],
732 frameA = [viewA frame],
733 viewB = _subviews[dividerIndex + 1],
734 frameB = [viewB frame],
735 preCollapsePosition = 0,
736 preSize = frameA.size[_sizeComponent];
738 frameA.size[_sizeComponent] = realPosition - frameA.origin[_originComponent];
740 if (preSize !== 0 && frameA.size[_sizeComponent] === 0)
741 preCollapsePosition = preSize;
743 if (preSize !== frameA.size[_sizeComponent])
746 [_subviews[dividerIndex] setFrame:frameA];
750 preSize = frameB.size[_sizeComponent];
752 var preOrigin = frameB.origin[_originComponent];
753 frameB.size[_sizeComponent] = frameB.origin[_originComponent] + frameB.size[_sizeComponent] - realPosition - [
self dividerThickness];
755 if (preSize !== 0 && frameB.size[_sizeComponent] === 0)
756 preCollapsePosition = frameB.origin[_originComponent];
760 if (preSize !== frameB.size[_sizeComponent] || preOrigin !== frameB.origin[_originComponent])
763 [_subviews[dividerIndex + 1] setFrame:frameB];
767 if (preCollapsePosition)
768 [_preCollapsePositions setObject:preCollapsePosition forKey:"" + dividerIndex];
773 [
self _postNotificationDidResize];
778 - (void)setFrameSize:(CGSize)aSize
780 if (_shouldRestoreFromAutosaveUnlessFrameSize)
781 _shouldAutosave = NO;
783 [
self _adjustSubviewsWithCalculatedSize];
787 if (_shouldRestoreFromAutosaveUnlessFrameSize)
788 _shouldAutosave = YES;
793 - (void)resizeSubviewsWithOldSize:(CPSize)oldSize
795 if ([_delegate respondsToSelector:
@selector(splitView:resizeSubviewsWithOldSize:)])
797 [_delegate splitView:self resizeSubviewsWithOldSize:oldSize];
804 - (void)adjustSubviews
806 var count = [_subviews count];
812 [
self _postNotificationWillResize];
816 boundsSize = bounds.size[_sizeComponent],
817 oldSize = [
self _calculateSize],
819 totalDividers = count - 1,
820 oldFlexibleSpace = 0,
821 totalSizablePanes = 0,
824 delegateRespondsToShouldAdjust = [_delegate respondsToSelector:@selector(splitView:shouldAdjustSizeOfSubview:)];
837 for (index = 0; index < count; ++index)
839 var view = _subviews[index],
840 isSizable = !delegateRespondsToShouldAdjust || [_delegate splitView:self shouldAdjustSizeOfSubview:view],
841 size = [view frame].size[_sizeComponent];
843 isSizableMap[index] = isSizable;
844 viewSizes.push(size);
848 oldFlexibleSpace += size;
854 var nonSizableSpace = oldSize[_sizeComponent] - oldFlexibleSpace,
855 newFlexibleSpace = boundsSize - nonSizableSpace,
856 remainingFixedPixelsToRemove = 0;
858 if (newFlexibleSpace < 0)
860 remainingFixedPixelsToRemove = -newFlexibleSpace;
861 newFlexibleSpace = 0;
864 var remainingFixedPanes = count - totalSizablePanes;
866 for (index = 0; index < count; ++index)
868 var view = _subviews[index],
869 viewFrame = CGRectMakeCopy(bounds),
870 isSizable = isSizableMap[index],
874 if (index + 1 === count)
875 targetSize = boundsSize - viewFrame.origin[_originComponent];
879 var removedFixedPixels = MIN(remainingFixedPixelsToRemove / remainingFixedPanes, viewSizes[index]);
880 targetSize = viewSizes[index] - removedFixedPixels;
881 remainingFixedPixelsToRemove -= removedFixedPixels;
882 remainingFixedPanes--;
885 else if (oldFlexibleSpace > 0)
886 targetSize = newFlexibleSpace * viewSizes[index] / oldFlexibleSpace;
890 targetSize = newFlexibleSpace / totalSizablePanes;
892 targetSize = MAX(0, ROUND(targetSize));
893 viewFrame.size[_sizeComponent] = targetSize;
894 [view setFrame:viewFrame];
895 bounds.origin[_originComponent] += targetSize + dividerThickness;
966 - (void)setDelegate:(
id)delegate
968 if ([_delegate respondsToSelector:
@selector(splitViewDidResizeSubviews:)])
970 if ([_delegate respondsToSelector:
@selector(splitViewWillResizeSubviews:)])
973 _delegate = delegate;
975 if ([_delegate respondsToSelector:
@selector(splitViewDidResizeSubviews:)])
978 name:CPSplitViewDidResizeSubviewsNotification
980 if ([_delegate respondsToSelector:
@selector(splitViewWillResizeSubviews:)])
983 name:CPSplitViewWillResizeSubviewsNotification
1003 - (void)setButtonBar:(
CPButtonBar)aButtonBar forDividerAtIndex:(
unsigned)dividerIndex
1007 _buttonBars[dividerIndex] = nil;
1012 subview = aButtonBar;
1014 while (view && view !==
self)
1017 view = [view superview];
1022 reason:@"CPSplitView button bar must be a subview of the split view."];
1029 _buttonBars[dividerIndex] = aButtonBar;
1032 - (void)_postNotificationWillResize
1044 - (void)_postNotificationDidResize
1068 if (_autosaveName == autosaveName)
1071 _autosaveName = autosaveName;
1081 return _autosaveName;
1089 if (_shouldRestoreFromAutosaveUnlessFrameSize || !_shouldAutosave || !_autosaveName)
1093 autosaveName = [
self _framesKeyForAutosaveName:[
self autosaveName]],
1094 autosavePrecollapseName = [
self _precollapseKeyForAutosaveName:[
self autosaveName]],
1095 count = [_subviews count],
1099 for (var i = 0; i < count; i++)
1101 var
frame = [_subviews[i] frame];
1102 [positions addObject:CPStringFromRect(frame)];
1103 [preCollapseArray addObject:[_preCollapsePositions objectForKey:"" + i]];
1106 [userDefaults setObject:positions forKey:autosaveName];
1107 [userDefaults setObject:preCollapseArray forKey:autosavePrecollapseName];
1116 - (void)_restoreFromAutosaveIfNeeded
1118 if (_shouldRestoreFromAutosaveUnlessFrameSize && !_CGSizeEqualToSize([
self frameSize], _shouldRestoreFromAutosaveUnlessFrameSize))
1120 [
self _restoreFromAutosave];
1123 _shouldRestoreFromAutosaveUnlessFrameSize = nil;
1129 - (void)_restoreFromAutosave
1134 var autosaveName = [
self _framesKeyForAutosaveName:[
self autosaveName]],
1135 autosavePrecollapseName = [
self _precollapseKeyForAutosaveName:[
self autosaveName]],
1137 frames = [userDefaults objectForKey:autosaveName],
1138 preCollapseArray = [userDefaults objectForKey:autosavePrecollapseName];
1142 var dividerThickness = [
self dividerThickness],
1145 _shouldAutosave = NO;
1147 for (var i = 0, count = [frames count] - 1; i < count; i++)
1150 position += frame.size[_sizeComponent];
1152 [
self setPosition:position ofDividerAtIndex:i];
1154 position += dividerThickness;
1157 _shouldAutosave = YES;
1160 if (preCollapseArray)
1164 for (var i = 0, count = [preCollapseArray count]; i < count; i++)
1165 [_preCollapsePositions setObject:preCollapseArray[i] forKey:i + ""];
1174 if (!theAutosaveName)
1177 return @"CPSplitView Subview Frames " + theAutosaveName;
1185 if (!theAutosaveName)
1188 return @"CPSplitView Subview Precollapse Positions " + theAutosaveName;
1208 _autosaveName = [aCoder decodeObjectForKey:CPSplitViewAutosaveNameKey];
1239 _suppressResizeNotificationsMask = 0;
1243 _shouldAutosave = YES;
1245 _DOMDividerElements = [];
1247 _buttonBars = [aCoder decodeObjectForKey:CPSplitViewButtonBarsKey] || [];
1249 [
self setDelegate:[aCoder decodeObjectForKey:CPSplitViewDelegateKey]];
1251 _isPaneSplitter = [aCoder decodeBoolForKey:CPSplitViewIsPaneSplitterKey];
1252 [
self _setVertical:[aCoder decodeBoolForKey:CPSplitViewIsVerticalKey]];
1256 [
self _restoreFromAutosave];
1259 _shouldRestoreFromAutosaveUnlessFrameSize = [
self frameSize];
1277 [aCoder encodeConditionalObject:_delegate forKey:CPSplitViewDelegateKey];
1279 [aCoder encodeBool:_isVertical forKey:CPSplitViewIsVerticalKey];
1280 [aCoder encodeBool:_isPaneSplitter forKey:CPSplitViewIsPaneSplitterKey];
1282 [aCoder encodeObject:_autosaveName forKey:CPSplitViewAutosaveNameKey];