![]() |
API 0.9.5
|
00001 /* 00002 * CPSplitView.j 00003 * AppKit 00004 * 00005 * Created by Thomas Robinson. 00006 * Copyright 2008, 280 North, Inc. 00007 * 00008 * This library is free software; you can redistribute it and/or 00009 * modify it under the terms of the GNU Lesser General Public 00010 * License as published by the Free Software Foundation; either 00011 * version 2.1 of the License, or (at your option) any later version. 00012 * 00013 * This library is distributed in the hope that it will be useful, 00014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00016 * Lesser General Public License for more details. 00017 * 00018 * You should have received a copy of the GNU Lesser General Public 00019 * License along with this library; if not, write to the Free Software 00020 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00021 */ 00022 00023 #include "../Foundation/Foundation.h" 00024 00025 00026 #define SPLIT_VIEW_MAYBE_POST_WILL_RESIZE() \ 00027 if ((_suppressResizeNotificationsMask & DidPostWillResizeNotification) === 0) \ 00028 { \ 00029 [self _postNotificationWillResize]; \ 00030 _suppressResizeNotificationsMask |= DidPostWillResizeNotification; \ 00031 } 00032 00033 #define SPLIT_VIEW_MAYBE_POST_DID_RESIZE() \ 00034 if ((_suppressResizeNotificationsMask & ShouldSuppressResizeNotifications) !== 0) \ 00035 _suppressResizeNotificationsMask |= DidSuppressResizeNotification; \ 00036 else \ 00037 [self _postNotificationDidResize]; 00038 00039 #define SPLIT_VIEW_DID_SUPPRESS_RESIZE_NOTIFICATION() \ 00040 ((_suppressResizeNotificationsMask & DidSuppressResizeNotification) !== 0) 00041 00042 #define SPLIT_VIEW_SUPPRESS_RESIZE_NOTIFICATIONS(shouldSuppress) \ 00043 if (shouldSuppress) \ 00044 _suppressResizeNotificationsMask |= ShouldSuppressResizeNotifications; \ 00045 else \ 00046 _suppressResizeNotificationsMask = 0; 00047 00048 CPSplitViewDidResizeSubviewsNotification = @"CPSplitViewDidResizeSubviewsNotification"; 00049 CPSplitViewWillResizeSubviewsNotification = @"CPSplitViewWillResizeSubviewsNotification"; 00050 00051 var CPSplitViewHorizontalImage = nil, 00052 CPSplitViewVerticalImage = nil, 00053 00054 ShouldSuppressResizeNotifications = 1, 00055 DidPostWillResizeNotification = 1 << 1, 00056 DidSuppressResizeNotification = 1 << 2; 00057 00070 @implementation CPSplitView : CPView 00071 { 00072 id _delegate; 00073 BOOL _isVertical; 00074 BOOL _isPaneSplitter; 00075 00076 int _currentDivider; 00077 float _initialOffset; 00078 CPDictionary _preCollapsePositions; 00079 00080 CPString _originComponent; 00081 CPString _sizeComponent; 00082 00083 CPArray _DOMDividerElements; 00084 CPString _dividerImagePath; 00085 int _drawingDivider; 00086 00087 CPString _autosaveName; 00088 BOOL _shouldAutosave; 00089 BOOL _needsRestoreFromAutosave; 00090 00091 BOOL _needsResizeSubviews; 00092 int _suppressResizeNotificationsMask; 00093 00094 CPArray _buttonBars; 00095 } 00096 00097 + (CPString)defaultThemeClass 00098 { 00099 return @"splitview"; 00100 } 00101 00102 + (id)themeAttributes 00103 { 00104 return [CPDictionary dictionaryWithObjects:[1.0, 10.0, [CPColor grayColor]] 00105 forKeys:[@"divider-thickness", @"pane-divider-thickness", @"pane-divider-color"]]; 00106 } 00107 00108 /* 00109 @ignore 00110 */ 00111 + (void)initialize 00112 { 00113 if (self != [CPSplitView class]) 00114 return; 00115 00116 var bundle = [CPBundle bundleForClass:self]; 00117 CPSplitViewHorizontalImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPSplitView/CPSplitViewHorizontal.png"] size:CPSizeMake(5.0, 10.0)]; 00118 CPSplitViewVerticalImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPSplitView/CPSplitViewVertical.png"] size:CPSizeMake(10.0, 5.0)]; 00119 } 00120 00121 - (id)initWithFrame:(CGRect)aFrame 00122 { 00123 if (self = [super initWithFrame:aFrame]) 00124 { 00125 _suppressResizeNotificationsMask = 0; 00126 _preCollapsePositions = [CPMutableDictionary new]; 00127 _currentDivider = CPNotFound; 00128 00129 _DOMDividerElements = []; 00130 _buttonBars = []; 00131 00132 _shouldAutosave = YES; 00133 00134 [self _setVertical:YES]; 00135 } 00136 00137 return self; 00138 } 00139 00144 - (float)dividerThickness 00145 { 00146 return [self currentValueForThemeAttribute:[self isPaneSplitter] ? @"pane-divider-thickness" : @"divider-thickness"]; 00147 } 00148 00153 - (BOOL)isVertical 00154 { 00155 return _isVertical; 00156 } 00157 00162 - (void)setVertical:(BOOL)shouldBeVertical 00163 { 00164 if (![self _setVertical:shouldBeVertical]) 00165 return; 00166 00167 // Just re-adjust evenly. 00168 var frame = [self frame], 00169 dividerThickness = [self dividerThickness]; 00170 00171 [self _postNotificationWillResize]; 00172 00173 var eachSize = ROUND((frame.size[_sizeComponent] - dividerThickness * (_subviews.length - 1)) / _subviews.length), 00174 index = 0, 00175 count = _subviews.length; 00176 00177 if ([self isVertical]) 00178 for (; index < count; ++index) 00179 [_subviews[index] setFrame:CGRectMake(ROUND((eachSize + dividerThickness) * index), 0, eachSize, frame.size.height)]; 00180 else 00181 for (; index < count; ++index) 00182 [_subviews[index] setFrame:CGRectMake(0, ROUND((eachSize + dividerThickness) * index), frame.size.width, eachSize)]; 00183 00184 [self setNeedsDisplay:YES]; 00185 00186 [self _postNotificationDidResize]; 00187 00188 } 00189 00190 - (BOOL)_setVertical:(BOOL)shouldBeVertical 00191 { 00192 var changed = (_isVertical != shouldBeVertical); 00193 00194 _isVertical = shouldBeVertical; 00195 00196 _originComponent = [self isVertical] ? "x" : "y"; 00197 _sizeComponent = [self isVertical] ? "width" : "height"; 00198 _dividerImagePath = [self isVertical] ? [CPSplitViewVerticalImage filename] : [CPSplitViewHorizontalImage filename]; 00199 00200 return changed; 00201 } 00202 00208 - (BOOL)isPaneSplitter 00209 { 00210 return _isPaneSplitter; 00211 } 00212 00218 - (void)setIsPaneSplitter:(BOOL)shouldBePaneSplitter 00219 { 00220 if (_isPaneSplitter == shouldBePaneSplitter) 00221 return; 00222 00223 _isPaneSplitter = shouldBePaneSplitter; 00224 00225 if (_DOMDividerElements[_drawingDivider]) 00226 [self _setupDOMDivider]; 00227 00228 // The divider changes size when pane splitter mode is toggled, so the 00229 // subviews need to change size too. 00230 _needsResizeSubviews = YES; 00231 [self setNeedsDisplay:YES]; 00232 } 00233 00234 - (void)didAddSubview:(CPView)aSubview 00235 { 00236 _needsResizeSubviews = YES; 00237 } 00238 00244 - (BOOL)isSubviewCollapsed:(CPView)subview 00245 { 00246 return [subview frame].size[_sizeComponent] < 1 ? YES : NO; 00247 } 00248 00255 - (CGRect)rectOfDividerAtIndex:(int)aDivider 00256 { 00257 var frame = [_subviews[aDivider] frame], 00258 rect = CGRectMakeZero(); 00259 00260 rect.size = [self frame].size; 00261 00262 rect.size[_sizeComponent] = [self dividerThickness]; 00263 rect.origin[_originComponent] = frame.origin[_originComponent] + frame.size[_sizeComponent]; 00264 00265 return rect; 00266 } 00267 00274 - (CGRect)effectiveRectOfDividerAtIndex:(int)aDivider 00275 { 00276 var realRect = [self rectOfDividerAtIndex:aDivider], 00277 padding = 2; 00278 00279 realRect.size[_sizeComponent] += padding * 2; 00280 realRect.origin[_originComponent] -= padding; 00281 00282 return realRect; 00283 } 00284 00285 - (void)drawRect:(CGRect)rect 00286 { 00287 var count = [_subviews count] - 1; 00288 00289 while ((count--) > 0) 00290 { 00291 _drawingDivider = count; 00292 [self drawDividerInRect:[self rectOfDividerAtIndex:count]]; 00293 } 00294 } 00295 00301 - (void)willRemoveSubview:(CPView)aView 00302 { 00303 #if PLATFORM(DOM) 00304 var dividerToRemove = _DOMDividerElements.pop(); 00305 00306 // The divider may not exist if we never rendered out the DOM. 00307 if (dividerToRemove) 00308 CPDOMDisplayServerRemoveChild(_DOMElement, dividerToRemove); 00309 #endif 00310 00311 _needsResizeSubviews = YES; 00312 [self setNeedsLayout]; 00313 [self setNeedsDisplay:YES]; 00314 } 00315 00316 - (void)layoutSubviews 00317 { 00318 [self _adjustSubviewsWithCalculatedSize] 00319 } 00320 00325 - (void)drawDividerInRect:(CGRect)aRect 00326 { 00327 #if PLATFORM(DOM) 00328 if (!_DOMDividerElements[_drawingDivider]) 00329 { 00330 _DOMDividerElements[_drawingDivider] = document.createElement("div"); 00331 00332 _DOMDividerElements[_drawingDivider].style.position = "absolute"; 00333 _DOMDividerElements[_drawingDivider].style.backgroundRepeat = "repeat"; 00334 00335 CPDOMDisplayServerAppendChild(_DOMElement, _DOMDividerElements[_drawingDivider]); 00336 } 00337 00338 [self _setupDOMDivider]; 00339 CPDOMDisplayServerSetStyleLeftTop(_DOMDividerElements[_drawingDivider], NULL, _CGRectGetMinX(aRect), _CGRectGetMinY(aRect)); 00340 CPDOMDisplayServerSetStyleSize(_DOMDividerElements[_drawingDivider], _CGRectGetWidth(aRect), _CGRectGetHeight(aRect)); 00341 #endif 00342 } 00343 00344 - (void)_setupDOMDivider 00345 { 00346 if (_isPaneSplitter) 00347 { 00348 _DOMDividerElements[_drawingDivider].style.backgroundColor = ""; 00349 _DOMDividerElements[_drawingDivider].style.backgroundImage = "url('"+_dividerImagePath+"')"; 00350 } 00351 else 00352 { 00353 _DOMDividerElements[_drawingDivider].style.backgroundColor = [[self currentValueForThemeAttribute:@"pane-divider-color"] cssString]; 00354 _DOMDividerElements[_drawingDivider].style.backgroundImage = ""; 00355 } 00356 } 00357 00358 - (void)viewWillDraw 00359 { 00360 [self _adjustSubviewsWithCalculatedSize]; 00361 } 00362 00363 - (void)_adjustSubviewsWithCalculatedSize 00364 { 00365 if (!_needsResizeSubviews) 00366 return; 00367 00368 _needsResizeSubviews = NO; 00369 00370 var subviews = [self subviews], 00371 count = subviews.length, 00372 oldSize = CGSizeMakeZero(); 00373 00374 if ([self isVertical]) 00375 { 00376 oldSize.width += [self dividerThickness] * (count - 1); 00377 oldSize.height = CGRectGetHeight([self frame]); 00378 } 00379 else 00380 { 00381 oldSize.width = CGRectGetWidth([self frame]); 00382 oldSize.height += [self dividerThickness] * (count - 1); 00383 } 00384 00385 while (count--) 00386 oldSize[_sizeComponent] += [subviews[count] frame].size[_sizeComponent]; 00387 00388 [self resizeSubviewsWithOldSize:oldSize]; 00389 } 00390 00391 - (BOOL)cursorAtPoint:(CPPoint)aPoint hitDividerAtIndex:(int)anIndex 00392 { 00393 var frame = [_subviews[anIndex] frame], 00394 startPosition = frame.origin[_originComponent] + frame.size[_sizeComponent], 00395 effectiveRect = [self effectiveRectOfDividerAtIndex:anIndex], 00396 buttonBar = _buttonBars[anIndex], 00397 buttonBarRect = null, 00398 additionalRect = null; 00399 00400 if (buttonBar != null) 00401 { 00402 buttonBarRect = [buttonBar resizeControlFrame]; 00403 buttonBarRect.origin = [self convertPoint:buttonBarRect.origin fromView:buttonBar]; 00404 } 00405 00406 if ([_delegate respondsToSelector:@selector(splitView:effectiveRect:forDrawnRect:ofDividerAtIndex:)]) 00407 effectiveRect = [_delegate splitView:self effectiveRect:effectiveRect forDrawnRect:effectiveRect ofDividerAtIndex:anIndex]; 00408 00409 if ([_delegate respondsToSelector:@selector(splitView:additionalEffectiveRectOfDividerAtIndex:)]) 00410 additionalRect = [_delegate splitView:self additionalEffectiveRectOfDividerAtIndex:anIndex]; 00411 00412 return CGRectContainsPoint(effectiveRect, aPoint) || 00413 (additionalRect && CGRectContainsPoint(additionalRect, aPoint)) || 00414 (buttonBarRect && CGRectContainsPoint(buttonBarRect, aPoint)); 00415 } 00416 00417 - (CPView)hitTest:(CGPoint)aPoint 00418 { 00419 if ([self isHidden] || ![self hitTests] || !CGRectContainsPoint([self frame], aPoint)) 00420 return nil; 00421 00422 var point = [self convertPoint:aPoint fromView:[self superview]], 00423 count = [_subviews count] - 1; 00424 00425 for (var i = 0; i < count; i++) 00426 { 00427 if ([self cursorAtPoint:point hitDividerAtIndex:i]) 00428 return self; 00429 } 00430 00431 return [super hitTest:aPoint]; 00432 } 00433 00434 /* 00435 Tracks the divider. 00436 @param anEvent the input event 00437 */ 00438 - (void)trackDivider:(CPEvent)anEvent 00439 { 00440 var type = [anEvent type]; 00441 00442 if (type == CPLeftMouseUp) 00443 { 00444 if (_currentDivider != CPNotFound) 00445 { 00446 _currentDivider = CPNotFound; 00447 [self _updateResizeCursor:anEvent]; 00448 } 00449 00450 return; 00451 } 00452 00453 if (type == CPLeftMouseDown) 00454 { 00455 var point = [self convertPoint:[anEvent locationInWindow] fromView:nil]; 00456 00457 _currentDivider = CPNotFound; 00458 var count = [_subviews count] - 1; 00459 for (var i = 0; i < count; i++) 00460 { 00461 var frame = [_subviews[i] frame], 00462 startPosition = frame.origin[_originComponent] + frame.size[_sizeComponent]; 00463 00464 if ([self cursorAtPoint:point hitDividerAtIndex:i]) 00465 { 00466 if ([anEvent clickCount] == 2 && 00467 [_delegate respondsToSelector:@selector(splitView:canCollapseSubview:)] && 00468 [_delegate respondsToSelector:@selector(splitView:shouldCollapseSubview:forDoubleClickOnDividerAtIndex:)]) 00469 { 00470 var minPosition = [self minPossiblePositionOfDividerAtIndex:i], 00471 maxPosition = [self maxPossiblePositionOfDividerAtIndex:i], 00472 _preCollapsePosition = [_preCollapsePositions objectForKey:"" + i] || 0; 00473 00474 if ([_delegate splitView:self canCollapseSubview:_subviews[i]] && [_delegate splitView:self shouldCollapseSubview:_subviews[i] forDoubleClickOnDividerAtIndex:i]) 00475 { 00476 if ([self isSubviewCollapsed:_subviews[i]]) 00477 [self setPosition:_preCollapsePosition ? _preCollapsePosition : (minPosition + (maxPosition - minPosition) / 2) ofDividerAtIndex:i]; 00478 else 00479 [self setPosition:minPosition ofDividerAtIndex:i]; 00480 } 00481 else if ([_delegate splitView:self canCollapseSubview:_subviews[i + 1]] && [_delegate splitView:self shouldCollapseSubview:_subviews[i + 1] forDoubleClickOnDividerAtIndex:i]) 00482 { 00483 if ([self isSubviewCollapsed:_subviews[i + 1]]) 00484 [self setPosition:_preCollapsePosition ? _preCollapsePosition : (minPosition + (maxPosition - minPosition) / 2) ofDividerAtIndex:i]; 00485 else 00486 [self setPosition:maxPosition ofDividerAtIndex:i]; 00487 } 00488 } 00489 else 00490 { 00491 _currentDivider = i; 00492 _initialOffset = startPosition - point[_originComponent]; 00493 00494 [self _postNotificationWillResize]; 00495 } 00496 } 00497 } 00498 00499 if (_currentDivider === CPNotFound) 00500 return; 00501 } 00502 00503 else if (type == CPLeftMouseDragged && _currentDivider != CPNotFound) 00504 { 00505 var point = [self convertPoint:[anEvent locationInWindow] fromView:nil]; 00506 00507 [self setPosition:(point[_originComponent] + _initialOffset) ofDividerAtIndex:_currentDivider]; 00508 // Cursor might change if we reach a resize limit. 00509 [self _updateResizeCursor:anEvent]; 00510 } 00511 00512 [CPApp setTarget:self selector:@selector(trackDivider:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES]; 00513 } 00514 00515 - (void)mouseDown:(CPEvent)anEvent 00516 { 00517 // FIXME: This should not trap events if not on a divider! 00518 [self trackDivider:anEvent]; 00519 } 00520 00521 - (void)viewDidMoveToWindow 00522 { 00523 // Enable split view resize cursors. Commented out pending CPTrackingArea implementation. 00524 //[[self window] setAcceptsMouseMovedEvents:YES]; 00525 } 00526 00527 - (void)mouseEntered:(CPEvent)anEvent 00528 { 00529 // Tracking code handles cursor by itself. 00530 if (_currentDivider == CPNotFound) 00531 [self _updateResizeCursor:anEvent]; 00532 } 00533 00534 - (void)mouseMoved:(CPEvent)anEvent 00535 { 00536 if (_currentDivider == CPNotFound) 00537 [self _updateResizeCursor:anEvent]; 00538 } 00539 00540 - (void)mouseExited:(CPEvent)anEvent 00541 { 00542 if (_currentDivider == CPNotFound) 00543 // FIXME: we should use CPCursor push/pop (if previous currentCursor != arrow). 00544 [[CPCursor arrowCursor] set]; 00545 } 00546 00547 - (void)_updateResizeCursor:(CPEvent)anEvent 00548 { 00549 var point = [self convertPoint:[anEvent locationInWindow] fromView:nil]; 00550 00551 if ([anEvent type] === CPLeftMouseUp && ![[self window] acceptsMouseMovedEvents]) 00552 { 00553 [[CPCursor arrowCursor] set]; 00554 return; 00555 } 00556 00557 for (var i = 0, count = [_subviews count] - 1; i < count; i++) 00558 { 00559 // If we are currently tracking, keep the resize cursor active even outside of hit areas. 00560 if (_currentDivider === i || (_currentDivider == CPNotFound && [self cursorAtPoint:point hitDividerAtIndex:i])) 00561 { 00562 var frameA = [_subviews[i] frame], 00563 sizeA = frameA.size[_sizeComponent], 00564 startPosition = frameA.origin[_originComponent] + sizeA, 00565 frameB = [_subviews[i + 1] frame], 00566 sizeB = frameB.size[_sizeComponent], 00567 canShrink = [self _realPositionForPosition:startPosition - 1 ofDividerAtIndex:i] < startPosition, 00568 canGrow = [self _realPositionForPosition:startPosition + 1 ofDividerAtIndex:i] > startPosition, 00569 cursor = [CPCursor arrowCursor]; 00570 00571 if (sizeA === 0) 00572 canGrow = YES; // Subview is collapsed. 00573 else if (!canShrink && 00574 [_delegate respondsToSelector:@selector(splitView:canCollapseSubview:)] && 00575 [_delegate splitView:self canCollapseSubview:_subviews[i]]) 00576 canShrink = YES; // Subview is collapsible. 00577 00578 if (sizeB === 0) 00579 { 00580 // Right/lower subview is collapsed. 00581 canGrow = NO; 00582 // It's safe to assume it can always be uncollapsed. 00583 canShrink = YES; 00584 } 00585 else if (!canGrow && 00586 [_delegate respondsToSelector:@selector(splitView:canCollapseSubview:)] && 00587 [_delegate splitView:self canCollapseSubview:_subviews[i + 1]]) 00588 canGrow = YES; // Right/lower subview is collapsible. 00589 00590 if (_isVertical && canShrink && canGrow) 00591 cursor = [CPCursor resizeLeftRightCursor]; 00592 else if (_isVertical && canShrink) 00593 cursor = [CPCursor resizeLeftCursor]; 00594 else if (_isVertical && canGrow) 00595 cursor = [CPCursor resizeRightCursor]; 00596 else if (canShrink && canGrow) 00597 cursor = [CPCursor resizeUpDownCursor]; 00598 else if (canShrink) 00599 cursor = [CPCursor resizeUpCursor]; 00600 else if (canGrow) 00601 cursor = [CPCursor resizeDownCursor]; 00602 00603 [cursor set]; 00604 return; 00605 } 00606 } 00607 00608 [[CPCursor arrowCursor] set]; 00609 } 00610 00616 - (float)maxPossiblePositionOfDividerAtIndex:(int)dividerIndex 00617 { 00618 var frame = [_subviews[dividerIndex + 1] frame]; 00619 00620 if (dividerIndex + 1 < [_subviews count] - 1) 00621 return frame.origin[_originComponent] + frame.size[_sizeComponent] - [self dividerThickness]; 00622 else 00623 return [self frame].size[_sizeComponent] - [self dividerThickness]; 00624 } 00625 00631 - (float)minPossiblePositionOfDividerAtIndex:(int)dividerIndex 00632 { 00633 if (dividerIndex > 0) 00634 { 00635 var frame = [_subviews[dividerIndex - 1] frame]; 00636 00637 return frame.origin[_originComponent] + frame.size[_sizeComponent] + [self dividerThickness]; 00638 } 00639 else 00640 return 0; 00641 } 00642 00643 - (int)_realPositionForPosition:(float)position ofDividerAtIndex:(int)dividerIndex 00644 { 00645 // not sure where this should override other positions? 00646 if ([_delegate respondsToSelector:@selector(splitView:constrainSplitPosition:ofSubviewAt:)]) 00647 { 00648 var proposedPosition = [_delegate splitView:self constrainSplitPosition:position ofSubviewAt:dividerIndex]; 00649 00650 // Silently ignore bad positions which could result from odd delegate responses. We don't want these 00651 // bad results to go into the system and cause havoc with frame sizes as the split view tries to resize 00652 // its subviews. 00653 if (_IS_NUMERIC(proposedPosition)) 00654 position = proposedPosition; 00655 } 00656 00657 var proposedMax = [self maxPossiblePositionOfDividerAtIndex:dividerIndex], 00658 proposedMin = [self minPossiblePositionOfDividerAtIndex:dividerIndex], 00659 actualMax = proposedMax, 00660 actualMin = proposedMin; 00661 00662 if ([_delegate respondsToSelector:@selector(splitView:constrainMinCoordinate:ofSubviewAt:)]) 00663 { 00664 var proposedActualMin = [_delegate splitView:self constrainMinCoordinate:proposedMin ofSubviewAt:dividerIndex]; 00665 if (_IS_NUMERIC(proposedActualMin)) 00666 actualMin = proposedActualMin; 00667 } 00668 00669 if ([_delegate respondsToSelector:@selector(splitView:constrainMaxCoordinate:ofSubviewAt:)]) 00670 { 00671 var proposedActualMax = [_delegate splitView:self constrainMaxCoordinate:proposedMax ofSubviewAt:dividerIndex]; 00672 if (_IS_NUMERIC(proposedActualMax)) 00673 actualMax = proposedActualMax; 00674 } 00675 00676 var viewA = _subviews[dividerIndex], 00677 viewB = _subviews[dividerIndex + 1], 00678 realPosition = MAX(MIN(position, actualMax), actualMin); 00679 00680 // Is this position past the halfway point to collapse? 00681 if (position < proposedMin + (actualMin - proposedMin) / 2) 00682 if ([_delegate respondsToSelector:@selector(splitView:canCollapseSubview:)]) 00683 if ([_delegate splitView:self canCollapseSubview:viewA]) 00684 realPosition = proposedMin; 00685 // We can also collapse to the right. 00686 if (position > proposedMax - (proposedMax - actualMax) / 2) 00687 if ([_delegate respondsToSelector:@selector(splitView:canCollapseSubview:)]) 00688 if ([_delegate splitView:self canCollapseSubview:viewB]) 00689 realPosition = proposedMax; 00690 00691 return realPosition; 00692 } 00693 00699 - (void)setPosition:(float)position ofDividerAtIndex:(int)dividerIndex 00700 { 00701 SPLIT_VIEW_SUPPRESS_RESIZE_NOTIFICATIONS(YES); 00702 [self _adjustSubviewsWithCalculatedSize]; 00703 00704 var realPosition = [self _realPositionForPosition:position ofDividerAtIndex:dividerIndex]; 00705 00706 var viewA = _subviews[dividerIndex], 00707 frameA = [viewA frame], 00708 viewB = _subviews[dividerIndex + 1], 00709 frameB = [viewB frame], 00710 _preCollapsePosition = 0; 00711 00712 var preSize = frameA.size[_sizeComponent]; 00713 frameA.size[_sizeComponent] = realPosition - frameA.origin[_originComponent]; 00714 if (preSize !== 0 && frameA.size[_sizeComponent] === 0) 00715 _preCollapsePosition = preSize; 00716 if (preSize !== frameA.size[_sizeComponent]) 00717 { 00718 SPLIT_VIEW_MAYBE_POST_WILL_RESIZE(); 00719 [_subviews[dividerIndex] setFrame:frameA]; 00720 SPLIT_VIEW_MAYBE_POST_DID_RESIZE(); 00721 } 00722 00723 preSize = frameB.size[_sizeComponent]; 00724 var preOrigin = frameB.origin[_originComponent]; 00725 frameB.size[_sizeComponent] = frameB.origin[_originComponent] + frameB.size[_sizeComponent] - realPosition - [self dividerThickness]; 00726 if (preSize !== 0 && frameB.size[_sizeComponent] === 0) 00727 _preCollapsePosition = frameB.origin[_originComponent]; 00728 frameB.origin[_originComponent] = realPosition + [self dividerThickness]; 00729 if (preSize !== frameB.size[_sizeComponent] || preOrigin !== frameB.origin[_originComponent]) 00730 { 00731 SPLIT_VIEW_MAYBE_POST_WILL_RESIZE(); 00732 [_subviews[dividerIndex + 1] setFrame:frameB]; 00733 SPLIT_VIEW_MAYBE_POST_DID_RESIZE(); 00734 } 00735 00736 if (_preCollapsePosition) 00737 [_preCollapsePositions setObject:_preCollapsePosition forKey:"" + dividerIndex]; 00738 00739 [self setNeedsDisplay:YES]; 00740 00741 if (SPLIT_VIEW_DID_SUPPRESS_RESIZE_NOTIFICATION()) 00742 [self _postNotificationDidResize]; 00743 SPLIT_VIEW_SUPPRESS_RESIZE_NOTIFICATIONS(NO); 00744 } 00745 00746 - (void)setFrameSize:(CGSize)aSize 00747 { 00748 if (_needsRestoreFromAutosave) 00749 _shouldAutosave = NO; 00750 else 00751 [self _adjustSubviewsWithCalculatedSize]; 00752 00753 [super setFrameSize:aSize]; 00754 00755 if (_needsRestoreFromAutosave) 00756 { 00757 _needsRestoreFromAutosave = NO; 00758 [self _restoreFromAutosave]; 00759 _shouldAutosave = YES; 00760 } 00761 00762 [self setNeedsDisplay:YES]; 00763 } 00764 00765 - (void)resizeSubviewsWithOldSize:(CPSize)oldSize 00766 { 00767 if ([_delegate respondsToSelector:@selector(splitView:resizeSubviewsWithOldSize:)]) 00768 { 00769 [_delegate splitView:self resizeSubviewsWithOldSize:oldSize]; 00770 return; 00771 } 00772 00773 SPLIT_VIEW_MAYBE_POST_WILL_RESIZE(); 00774 [self _postNotificationWillResize]; 00775 00776 var index = 0, 00777 count = [_subviews count], 00778 bounds = [self bounds], 00779 dividerThickness = [self dividerThickness], 00780 totalDividers = count - 1, 00781 totalSizableSpace = 0, 00782 nonSizableSpace = 0, 00783 lastSizableIndex = -1, 00784 totalSizablePanes = 0, 00785 isVertical = [self isVertical]; 00786 00787 for (index = 0; index < count; ++index) 00788 { 00789 var view = _subviews[index], 00790 isSizable = isVertical ? [view autoresizingMask] & CPViewWidthSizable : [view autoresizingMask] & CPViewHeightSizable; 00791 00792 if (isSizable) 00793 { 00794 totalSizableSpace += [view frame].size[_sizeComponent]; 00795 lastSizableIndex = index; 00796 totalSizablePanes++; 00797 } 00798 } 00799 00800 if (totalSizablePanes === count) 00801 totalSizableSpace = 0; 00802 00803 var nonSizableSpace = totalSizableSpace ? bounds.size[_sizeComponent] - totalSizableSpace : 0, 00804 remainingFlexibleSpace = bounds.size[_sizeComponent] - oldSize[_sizeComponent], 00805 oldDimension = (oldSize[_sizeComponent] - totalDividers * dividerThickness - nonSizableSpace), 00806 ratio = oldDimension <= 0 ? 0 : (bounds.size[_sizeComponent] - totalDividers * dividerThickness - nonSizableSpace) / oldDimension; 00807 00808 for (index = 0; index < count; ++index) 00809 { 00810 var view = _subviews[index], 00811 viewFrame = CGRectMakeCopy(bounds), 00812 isSizable = isVertical ? [view autoresizingMask] & CPViewWidthSizable : [view autoresizingMask] & CPViewHeightSizable; 00813 00814 if (index + 1 === count) 00815 viewFrame.size[_sizeComponent] = bounds.size[_sizeComponent] - viewFrame.origin[_originComponent]; 00816 00817 else if (totalSizableSpace && isSizable && lastSizableIndex === index) 00818 viewFrame.size[_sizeComponent] = MAX(0, ROUND([view frame].size[_sizeComponent] + remainingFlexibleSpace)) 00819 00820 else if (isSizable || !totalSizableSpace) 00821 { 00822 viewFrame.size[_sizeComponent] = MAX(0, ROUND(ratio * [view frame].size[_sizeComponent])); 00823 remainingFlexibleSpace -= (viewFrame.size[_sizeComponent] - [view frame].size[_sizeComponent]); 00824 } 00825 00826 else if (totalSizableSpace && !isSizable) 00827 viewFrame.size[_sizeComponent] = [view frame].size[_sizeComponent]; 00828 00829 bounds.origin[_originComponent] += viewFrame.size[_sizeComponent] + dividerThickness; 00830 00831 [view setFrame:viewFrame]; 00832 00833 } 00834 00835 SPLIT_VIEW_MAYBE_POST_DID_RESIZE(); 00836 } 00837 00898 - (void)setDelegate:(id)delegate 00899 { 00900 if ([_delegate respondsToSelector:@selector(splitViewDidResizeSubviews:)]) 00901 [[CPNotificationCenter defaultCenter] removeObserver:_delegate name:CPSplitViewDidResizeSubviewsNotification object:self]; 00902 if ([_delegate respondsToSelector:@selector(splitViewWillResizeSubviews:)]) 00903 [[CPNotificationCenter defaultCenter] removeObserver:_delegate name:CPSplitViewWillResizeSubviewsNotification object:self]; 00904 00905 _delegate = delegate; 00906 00907 if ([_delegate respondsToSelector:@selector(splitViewDidResizeSubviews:)]) 00908 [[CPNotificationCenter defaultCenter] addObserver:_delegate 00909 selector:@selector(splitViewDidResizeSubviews:) 00910 name:CPSplitViewDidResizeSubviewsNotification 00911 object:self]; 00912 if ([_delegate respondsToSelector:@selector(splitViewWillResizeSubviews:)]) 00913 [[CPNotificationCenter defaultCenter] addObserver:_delegate 00914 selector:@selector(splitViewWillResizeSubviews:) 00915 name:CPSplitViewWillResizeSubviewsNotification 00916 object:self]; 00917 } 00918 00934 // FIXME Should be renamed to setButtonBar:ofDividerAtIndex:. 00935 - (void)setButtonBar:(CPButtonBar)aButtonBar forDividerAtIndex:(unsigned)dividerIndex 00936 { 00937 if (!aButtonBar) 00938 { 00939 _buttonBars[dividerIndex] = nil; 00940 return; 00941 } 00942 00943 var view = [aButtonBar superview], 00944 subview = aButtonBar; 00945 00946 while (view && view !== self) 00947 { 00948 subview = view; 00949 view = [view superview]; 00950 } 00951 00952 if (view !== self) 00953 [CPException raise:CPInvalidArgumentException 00954 reason:@"CPSplitView button bar must be a subview of the split view."]; 00955 00956 var viewIndex = [[self subviews] indexOfObject:subview]; 00957 00958 [aButtonBar setHasResizeControl:YES]; 00959 [aButtonBar setResizeControlIsLeftAligned:dividerIndex < viewIndex]; 00960 00961 _buttonBars[dividerIndex] = aButtonBar; 00962 } 00963 00964 - (void)_postNotificationWillResize 00965 { 00966 [[CPNotificationCenter defaultCenter] postNotificationName:CPSplitViewWillResizeSubviewsNotification object:self]; 00967 } 00968 00969 - (void)_postNotificationDidResize 00970 { 00971 [self _autosave]; 00972 [[CPNotificationCenter defaultCenter] postNotificationName:CPSplitViewDidResizeSubviewsNotification object:self]; 00973 } 00974 00980 - (void)setAutosaveName:(CPString)autosaveName 00981 { 00982 if (_autosaveName == autosaveName) 00983 return; 00984 _autosaveName = autosaveName; 00985 } 00986 00992 - (CPString)autosaveName 00993 { 00994 return _autosaveName; 00995 } 00996 01000 - (void)_autosave 01001 { 01002 if (!_shouldAutosave) 01003 return; 01004 01005 var userDefaults = [CPUserDefaults standardUserDefaults], 01006 autosaveName = [self _framesKeyForAutosaveName:[self autosaveName]], 01007 autosavePrecollapseName = [self _precollapseKeyForAutosaveName:[self autosaveName]], 01008 count = [_subviews count], 01009 positions = [CPMutableArray new], 01010 preCollapseArray = [CPMutableArray new]; 01011 01012 for (var i = 0; i < count; i++) 01013 { 01014 var frame = [_subviews[i] frame]; 01015 [positions addObject:CPStringFromRect(frame)]; 01016 [preCollapseArray addObject:[_preCollapsePositions objectForKey:"" + i]]; 01017 } 01018 01019 [userDefaults setObject:positions forKey:autosaveName]; 01020 [userDefaults setObject:preCollapseArray forKey:autosavePrecollapseName]; 01021 } 01022 01026 - (void)_restoreFromAutosave 01027 { 01028 if (!_autosaveName) 01029 return; 01030 01031 var autosaveName = [self _framesKeyForAutosaveName:[self autosaveName]], 01032 autosavePrecollapseName = [self _precollapseKeyForAutosaveName:[self autosaveName]], 01033 userDefaults = [CPUserDefaults standardUserDefaults], 01034 frames = [userDefaults objectForKey:autosaveName], 01035 preCollapseArray = [userDefaults objectForKey:autosavePrecollapseName]; 01036 01037 if (frames) 01038 { 01039 var dividerThickness = [self dividerThickness], 01040 position = 0; 01041 01042 _shouldAutosave = NO; 01043 for (var i = 0, count = [frames count] - 1; i < count; i++) 01044 { 01045 var frame = CPRectFromString(frames[i]); 01046 position += frame.size[_sizeComponent]; 01047 01048 [self setPosition:position ofDividerAtIndex:i]; 01049 01050 position += dividerThickness; 01051 } 01052 _shouldAutosave = YES; 01053 } 01054 01055 if (preCollapseArray) 01056 { 01057 _preCollapsePositions = [CPMutableDictionary new]; 01058 for (var i = 0, count = [preCollapseArray count]; i < count; i++) 01059 [_preCollapsePositions setObject:preCollapseArray[i] forKey:i + ""]; 01060 } 01061 } 01062 01066 - (CPString)_framesKeyForAutosaveName:(CPString)theAutosaveName 01067 { 01068 return @"CPSplitView Subview Frames " + theAutosaveName; 01069 } 01070 01074 - (CPString)_precollapseKeyForAutosaveName:(CPString)theAutosaveName 01075 { 01076 return @"CPSplitView Subview Precollapse Positions " + theAutosaveName; 01077 } 01078 01079 @end 01080 01081 var CPSplitViewDelegateKey = "CPSplitViewDelegateKey", 01082 CPSplitViewIsVerticalKey = "CPSplitViewIsVerticalKey", 01083 CPSplitViewIsPaneSplitterKey = "CPSplitViewIsPaneSplitterKey", 01084 CPSplitViewButtonBarsKey = "CPSplitViewButtonBarsKey", 01085 CPSplitViewAutosaveNameKey = "CPSplitViewAutosaveNameKey"; 01086 01087 @implementation CPSplitView (CPCoding) 01088 01089 /* 01090 Initializes the split view by unarchiving data from \c aCoder. 01091 @param aCoder the coder containing the archived CPSplitView. 01092 */ 01093 - (id)initWithCoder:(CPCoder)aCoder 01094 { 01095 self = [super initWithCoder:aCoder]; 01096 01097 if (self) 01098 { 01099 _suppressResizeNotificationsMask = 0; 01100 _preCollapsePositions = [CPMutableDictionary new]; 01101 01102 _currentDivider = CPNotFound; 01103 _shouldAutosave = YES; 01104 01105 _DOMDividerElements = []; 01106 01107 _buttonBars = [aCoder decodeObjectForKey:CPSplitViewButtonBarsKey] || []; 01108 01109 [self setDelegate:[aCoder decodeObjectForKey:CPSplitViewDelegateKey]]; 01110 01111 _isPaneSplitter = [aCoder decodeBoolForKey:CPSplitViewIsPaneSplitterKey]; 01112 [self _setVertical:[aCoder decodeBoolForKey:CPSplitViewIsVerticalKey]]; 01113 01114 [self setAutosaveName:[aCoder decodeObjectForKey:CPSplitViewAutosaveNameKey]]; 01115 01116 // We have to wait until we know our frame size before restoring, or the frame resize later will throw 01117 // away the restored size. 01118 if (_autosaveName) 01119 _needsRestoreFromAutosave = YES; 01120 } 01121 01122 return self; 01123 } 01124 01125 /* 01126 Archives this split view into the provided coder. 01127 @param aCoder the coder to which the button's instance data will be written. 01128 */ 01129 - (void)encodeWithCoder:(CPCoder)aCoder 01130 { 01131 [super encodeWithCoder:aCoder]; 01132 01133 //FIXME how should we handle this? 01134 //[aCoder encodeObject:_buttonBars forKey:CPSplitViewButtonBarsKey]; 01135 01136 [aCoder encodeConditionalObject:_delegate forKey:CPSplitViewDelegateKey]; 01137 01138 [aCoder encodeBool:_isVertical forKey:CPSplitViewIsVerticalKey]; 01139 [aCoder encodeBool:_isPaneSplitter forKey:CPSplitViewIsPaneSplitterKey]; 01140 01141 [aCoder encodeObject:_autosaveName forKey:CPSplitViewAutosaveNameKey]; 01142 } 01143 01144 @end