00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 @import "CPImage.j"
00024 @import "CPView.j"
00025
00026 #include "CoreGraphics/CGGeometry.h"
00027 #include "Platform/Platform.h"
00028 #include "Platform/DOM/CPDOMDisplayServer.h"
00029
00030
00031 CPSplitViewDidResizeSubviewsNotification = @"CPSplitViewDidResizeSubviewsNotification";
00032 CPSplitViewWillResizeSubviewsNotification = @"CPSplitViewWillResizeSubviewsNotification";
00033
00034 var CPSplitViewHorizontalImage = nil,
00035 CPSplitViewVerticalImage = nil;
00036
00041 @implementation CPSplitView : CPView
00042 {
00043 id _delegate;
00044 BOOL _isVertical;
00045 BOOL _isPaneSplitter;
00046
00047 int _currentDivider;
00048 float _initialOffset;
00049
00050 CPString _originComponent;
00051 CPString _sizeComponent;
00052
00053 CPArray _DOMDividerElements;
00054 CPString _dividerImagePath;
00055 int _drawingDivider;
00056
00057 BOOL _needsResizeSubviews;
00058 }
00059
00060
00061
00062
00063 + (void)initialize
00064 {
00065 if (self != [CPSplitView class])
00066 return;
00067
00068 var bundle = [CPBundle bundleForClass:self];
00069 CPSplitViewHorizontalImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPSplitView/CPSplitViewHorizontal.png"] size:CPSizeMake(5.0, 10.0)];
00070 CPSplitViewVerticalImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPSplitView/CPSplitViewVertical.png"] size:CPSizeMake(10.0, 5.0)];
00071 }
00072
00073 - (id)initWithFrame:(CGRect)aFrame
00074 {
00075 if (self = [super initWithFrame:aFrame])
00076 {
00077 _currentDivider = CPNotFound;
00078
00079 _DOMDividerElements = [];
00080
00081 [self _setVertical:YES];
00082 }
00083
00084 return self;
00085 }
00086
00087 - (float)dividerThickness
00088 {
00089 return _isPaneSplitter ? 1.0 : 10.0;
00090 }
00091
00092 - (BOOL)isVertical
00093 {
00094 return _isVertical;
00095 }
00096
00097 - (void)setVertical:(BOOL)shouldBeVertical
00098 {
00099 if (![self _setVertical:shouldBeVertical])
00100 return;
00101
00102
00103 var frame = [self frame],
00104 dividerThickness = [self dividerThickness];
00105
00106 [self _postNotificationWillResize];
00107
00108 var eachSize = ROUND((frame.size[_sizeComponent] - dividerThickness * (_subviews.length - 1)) / _subviews.length),
00109 index = 0,
00110 count = _subviews.length;
00111
00112 if ([self isVertical])
00113 for (; index < count; ++index)
00114 [_subviews[index] setFrame:CGRectMake(ROUND((eachSize + dividerThickness) * index), 0, eachSize, frame.size.height)];
00115 else
00116 for (; index < count; ++index)
00117 [_subviews[index] setFrame:CGRectMake(0, ROUND((eachSize + dividerThickness) * index), frame.size.width, eachSize)];
00118
00119 [self setNeedsDisplay:YES];
00120
00121 [self _postNotificationDidResize];
00122
00123 }
00124
00125 - (BOOL)_setVertical:(BOOL)shouldBeVertical
00126 {
00127 var changed = (_isVertical != shouldBeVertical);
00128
00129 _isVertical = shouldBeVertical;
00130
00131 _originComponent = [self isVertical] ? "x" : "y";
00132 _sizeComponent = [self isVertical] ? "width" : "height";
00133 _dividerImagePath = [self isVertical] ? [CPSplitViewVerticalImage filename] : [CPSplitViewHorizontalImage filename];
00134
00135 return changed;
00136 }
00137
00138 - (BOOL)isPaneSplitter
00139 {
00140 return _isPaneSplitter;
00141 }
00142
00143 - (void)setIsPaneSplitter:(BOOL)shouldBePaneSplitter
00144 {
00145 if (_isPaneSplitter == shouldBePaneSplitter)
00146 return;
00147
00148 _isPaneSplitter = shouldBePaneSplitter;
00149
00150 if(_DOMDividerElements[_drawingDivider])
00151 [self _setupDOMDivider]
00152
00153
00154
00155 _needsResizeSubviews = YES;
00156 [self setNeedsDisplay:YES];
00157 }
00158
00159 - (void)didAddSubview:(CPView)aSubview
00160 {
00161 _needsResizeSubviews = YES;
00162 }
00163
00164 - (BOOL)isSubviewCollapsed:(CPView)subview
00165 {
00166 return [subview frame].size[_sizeComponent] < 1 ? YES : NO;
00167 }
00168
00169 - (CGRect)rectOfDividerAtIndex:(int)aDivider
00170 {
00171 var frame = [_subviews[aDivider] frame],
00172 rect = CGRectMakeZero();
00173
00174 rect.size = [self frame].size;
00175
00176 rect.size[_sizeComponent] = [self dividerThickness];
00177 rect.origin[_originComponent] = frame.origin[_originComponent] + frame.size[_sizeComponent];
00178
00179 return rect;
00180 }
00181
00182 - (CGRect)effectiveRectOfDividerAtIndex:(int)aDivider
00183 {
00184 var realRect = [self rectOfDividerAtIndex:aDivider];
00185
00186 var padding = 2;
00187
00188 realRect.size[_sizeComponent] += padding * 2;
00189 realRect.origin[_originComponent] -= padding;
00190
00191 return realRect;
00192 }
00193
00194 - (void)drawRect:(CGRect)rect
00195 {
00196 var count = [_subviews count] - 1;
00197
00198 while ((count--) > 0)
00199 {
00200 _drawingDivider = count;
00201 [self drawDividerInRect:[self rectOfDividerAtIndex:count]];
00202 }
00203 }
00204
00205 - (void)drawDividerInRect:(CGRect)aRect
00206 {
00207 #if PLATFORM(DOM)
00208 if (!_DOMDividerElements[_drawingDivider])
00209 {
00210 _DOMDividerElements[_drawingDivider] = document.createElement("div");
00211
00212 if(_isVertical)
00213 _DOMDividerElements[_drawingDivider].style.cursor = [[CPCursor resizeLeftRightCursor] _cssString];
00214 else
00215 _DOMDividerElements[_drawingDivider].style.cursor = [[CPCursor resizeUpDownCursor] _cssString];
00216
00217 _DOMDividerElements[_drawingDivider].style.position = "absolute";
00218 _DOMDividerElements[_drawingDivider].style.backgroundRepeat = "repeat";
00219
00220 CPDOMDisplayServerAppendChild(_DOMElement, _DOMDividerElements[_drawingDivider]);
00221
00222 [self _setupDOMDivider];
00223 }
00224
00225 CPDOMDisplayServerSetStyleLeftTop(_DOMDividerElements[_drawingDivider], NULL, _CGRectGetMinX(aRect), _CGRectGetMinY(aRect));
00226 CPDOMDisplayServerSetStyleSize(_DOMDividerElements[_drawingDivider], _CGRectGetWidth(aRect), _CGRectGetHeight(aRect));
00227 #endif
00228 }
00229
00230 - (void)_setupDOMDivider
00231 {
00232 if (_isPaneSplitter)
00233 {
00234 _DOMDividerElements[_drawingDivider].style.backgroundColor = "#A5A5A5";
00235 _DOMDividerElements[_drawingDivider].style.backgroundImage = "";
00236 }
00237 else
00238 {
00239 _DOMDividerElements[_drawingDivider].style.backgroundColor = "";
00240 _DOMDividerElements[_drawingDivider].style.backgroundImage = "url('"+_dividerImagePath+"')";
00241 }
00242 }
00243
00244 - (void)viewWillDraw
00245 {
00246 [self _adjustSubviewsWithCalculatedSize];
00247 }
00248
00249 - (void)_adjustSubviewsWithCalculatedSize
00250 {
00251 if (!_needsResizeSubviews)
00252 return;
00253
00254 _needsResizeSubviews = NO;
00255
00256 var subviews = [self subviews],
00257 count = subviews.length,
00258 oldSize = CGSizeMakeZero();
00259
00260 if ([self isVertical])
00261 {
00262 oldSize.width += [self dividerThickness] * (count - 1);
00263 oldSize.height = CGRectGetHeight([self frame]);
00264 }
00265 else
00266 {
00267 oldSize.width = CGRectGetWidth([self frame]);
00268 oldSize.height += [self dividerThickness] * (count - 1);
00269 }
00270
00271 while (count--)
00272 oldSize[_sizeComponent] += [subviews[count] frame].size[_sizeComponent];
00273
00274 [self resizeSubviewsWithOldSize:oldSize];
00275 }
00276
00277 - (BOOL)cursorAtPoint:(CPPoint)aPoint hitDividerAtIndex:(int)anIndex
00278 {
00279 var frame = [_subviews[anIndex] frame],
00280 startPosition = frame.origin[_originComponent] + frame.size[_sizeComponent],
00281 effectiveRect = [self effectiveRectOfDividerAtIndex:anIndex],
00282 additionalRect = null;
00283
00284 if ([_delegate respondsToSelector:@selector(splitView:effectiveRect:forDrawnRect:ofDividerAtIndex:)])
00285 effectiveRect = [_delegate splitView:self effectiveRect:effectiveRect forDrawnRect:effectiveRect ofDividerAtIndex:anIndex];
00286
00287 if ([_delegate respondsToSelector:@selector(splitView:additionalEffectiveRectOfDividerAtIndex:)])
00288 additionalRect = [_delegate splitView:self additionalEffectiveRectOfDividerAtIndex:anIndex];
00289
00290 return CGRectContainsPoint(effectiveRect, aPoint) || (additionalRect && CGRectContainsPoint(additionalRect, aPoint));
00291 }
00292
00293 - (CPView)hitTest:(CGPoint)aPoint
00294 {
00295 if ([self isHidden] || ![self hitTests] || !CGRectContainsPoint([self frame], aPoint))
00296 return nil;
00297
00298 var point = [self convertPoint:aPoint fromView:[self superview]];
00299
00300 var count = [_subviews count] - 1;
00301 for (var i = 0; i < count; i++)
00302 {
00303 if ([self cursorAtPoint:point hitDividerAtIndex:i])
00304 return self;
00305 }
00306
00307 return [super hitTest:aPoint];
00308 }
00309
00310
00311
00312
00313
00314 - (void)trackDivider:(CPEvent)anEvent
00315 {
00316 var type = [anEvent type];
00317
00318 if (type == CPLeftMouseUp)
00319 {
00320 if (_currentDivider != CPNotFound)
00321 {
00322 _currentDivider = CPNotFound;
00323 [self _postNotificationDidResize];
00324 }
00325
00326 return;
00327 }
00328
00329 if (type == CPLeftMouseDown)
00330 {
00331 var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
00332
00333 _currentDivider = CPNotFound;
00334 var count = [_subviews count] - 1;
00335 for (var i = 0; i < count; i++)
00336 {
00337 var frame = [_subviews[i] frame],
00338 startPosition = frame.origin[_originComponent] + frame.size[_sizeComponent];
00339
00340 if ([self cursorAtPoint:point hitDividerAtIndex:i])
00341 {
00342 if ([anEvent clickCount] == 2 &&
00343 [_delegate respondsToSelector:@selector(splitView:canCollapseSubview:)] &&
00344 [_delegate respondsToSelector:@selector(splitView:shouldCollapseSubview:forDoubleClickOnDividerAtIndex:)])
00345 {
00346 var minPosition = [self minPossiblePositionOfDividerAtIndex:i],
00347 maxPosition = [self maxPossiblePositionOfDividerAtIndex:i];
00348
00349 if ([_delegate splitView:self canCollapseSubview:_subviews[i]] && [_delegate splitView:self shouldCollapseSubview:_subviews[i] forDoubleClickOnDividerAtIndex:i])
00350 {
00351 if ([self isSubviewCollapsed:_subviews[i]])
00352 [self setPosition:(minPosition + (maxPosition - minPosition) / 2) ofDividerAtIndex:i];
00353 else
00354 [self setPosition:minPosition ofDividerAtIndex:i];
00355 }
00356 else if ([_delegate splitView:self canCollapseSubview:_subviews[i+1]] && [_delegate splitView:self shouldCollapseSubview:_subviews[i+1] forDoubleClickOnDividerAtIndex:i])
00357 {
00358 if ([self isSubviewCollapsed:_subviews[i+1]])
00359 [self setPosition:(minPosition + (maxPosition - minPosition) / 2) ofDividerAtIndex:i];
00360 else
00361 [self setPosition:maxPosition ofDividerAtIndex:i];
00362 }
00363 }
00364 else
00365 {
00366 _currentDivider = i;
00367 _initialOffset = startPosition - point[_originComponent];
00368
00369 [self _postNotificationWillResize];
00370 }
00371 }
00372 }
00373
00374 if (_currentDivider === CPNotFound)
00375 return;
00376 }
00377
00378 else if (type == CPLeftMouseDragged && _currentDivider != CPNotFound)
00379 {
00380 var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
00381
00382 [self setPosition:(point[_originComponent] + _initialOffset) ofDividerAtIndex:_currentDivider];
00383 }
00384
00385 [CPApp setTarget:self selector:@selector(trackDivider:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
00386 }
00387
00388 - (void)mouseDown:(CPEvent)anEvent
00389 {
00390
00391 [self trackDivider:anEvent];
00392 }
00393
00394 - (float)maxPossiblePositionOfDividerAtIndex:(int)dividerIndex
00395 {
00396 var frame = [_subviews[dividerIndex + 1] frame];
00397
00398 if (dividerIndex + 1 < [_subviews count] - 1)
00399 return frame.origin[_originComponent] + frame.size[_sizeComponent] - [self dividerThickness];
00400 else
00401 return [self frame].size[_sizeComponent] - [self dividerThickness];
00402 }
00403
00404 - (float)minPossiblePositionOfDividerAtIndex:(int)dividerIndex
00405 {
00406 if (dividerIndex > 0)
00407 {
00408 var frame = [_subviews[dividerIndex - 1] frame];
00409
00410 return frame.origin[_originComponent] + frame.size[_sizeComponent] + [self dividerThickness];
00411 }
00412 else
00413 return 0;
00414 }
00415
00416 - (void)setPosition:(float)position ofDividerAtIndex:(int)dividerIndex
00417 {
00418 [self _adjustSubviewsWithCalculatedSize];
00419
00420
00421 if ([_delegate respondsToSelector:@selector(splitView:constrainSplitPosition:ofSubviewAt:)])
00422 position = [_delegate splitView:self constrainSplitPosition:position ofSubviewAt:dividerIndex];
00423
00424 var proposedMax = [self maxPossiblePositionOfDividerAtIndex:dividerIndex],
00425 proposedMin = [self minPossiblePositionOfDividerAtIndex:dividerIndex],
00426 actualMax = proposedMax,
00427 actualMin = proposedMin;
00428
00429 if([_delegate respondsToSelector:@selector(splitView:constrainMinCoordinate:ofSubviewAt:)])
00430 actualMin = [_delegate splitView:self constrainMinCoordinate:proposedMin ofSubviewAt:dividerIndex];
00431
00432 if([_delegate respondsToSelector:@selector(splitView:constrainMaxCoordinate:ofSubviewAt:)])
00433 actualMax = [_delegate splitView:self constrainMaxCoordinate:proposedMax ofSubviewAt:dividerIndex];
00434
00435 var frame = [self frame],
00436 viewA = _subviews[dividerIndex],
00437 frameA = [viewA frame],
00438 viewB = _subviews[dividerIndex + 1],
00439 frameB = [viewB frame];
00440
00441 var realPosition = MAX(MIN(position, actualMax), actualMin);
00442
00443 if (position < proposedMin + (actualMin - proposedMin) / 2)
00444 if ([_delegate respondsToSelector:@selector(splitView:canCollapseSubview:)])
00445 if ([_delegate splitView:self canCollapseSubview:viewA])
00446 realPosition = proposedMin;
00447
00448 frameA.size[_sizeComponent] = realPosition - frameA.origin[_originComponent];
00449 [_subviews[dividerIndex] setFrame:frameA];
00450
00451 frameB.size[_sizeComponent] = frameB.origin[_originComponent] + frameB.size[_sizeComponent] - realPosition - [self dividerThickness];
00452 frameB.origin[_originComponent] = realPosition + [self dividerThickness];
00453 [_subviews[dividerIndex + 1] setFrame:frameB];
00454
00455 [self setNeedsDisplay:YES];
00456 }
00457
00458 - (void)setFrameSize:(CGSize)aSize
00459 {
00460 [self _adjustSubviewsWithCalculatedSize];
00461
00462 [super setFrameSize:aSize];
00463
00464 [self setNeedsDisplay:YES];
00465 }
00466
00467 - (void)resizeSubviewsWithOldSize:(CPSize)oldSize
00468 {
00469 if ([_delegate respondsToSelector:@selector(splitView:resizeSubviewsWithOldSize:)])
00470 {
00471 [_delegate splitView:self resizeSubviewsWithOldSize:oldSize];
00472 return;
00473 }
00474
00475 [self _postNotificationWillResize];
00476
00477 var index = 0,
00478 count = [_subviews count],
00479 bounds = [self bounds],
00480 dividerThickness = [self dividerThickness],
00481 totalDividers = count - 1,
00482 totalSizableSpace = 0,
00483 nonSizableSpace = 0,
00484 lastSizableIndex = -1,
00485 totalSizablePanes = 0,
00486 isVertical = [self isVertical];
00487
00488 for (index = 0; index < count; ++index)
00489 {
00490 var view = _subviews[index],
00491 isSizable = isVertical ? [view autoresizingMask] & CPViewWidthSizable : [view autoresizingMask] & CPViewHeightSizable;
00492
00493 if (isSizable)
00494 {
00495 totalSizableSpace += [view frame].size[_sizeComponent];
00496 lastSizableIndex = index;
00497 totalSizablePanes++;
00498 }
00499 }
00500
00501 if (totalSizablePanes === count)
00502 totalSizableSpace = 0;
00503
00504 var nonSizableSpace = totalSizableSpace ? bounds.size[_sizeComponent] - totalSizableSpace : 0,
00505 remainingFlexibleSpace = bounds.size[_sizeComponent] - oldSize[_sizeComponent],
00506 oldDimension = (oldSize[_sizeComponent]- totalDividers*dividerThickness - nonSizableSpace),
00507 ratio = oldDimension <= 0 ? 0 : (bounds.size[_sizeComponent] - totalDividers*dividerThickness - nonSizableSpace) / oldDimension;
00508
00509 for (index = 0; index < count; ++index)
00510 {
00511 var view = _subviews[index],
00512 viewFrame = CGRectMakeCopy(bounds),
00513 isSizable = isVertical ? [view autoresizingMask] & CPViewWidthSizable : [view autoresizingMask] & CPViewHeightSizable;
00514
00515 if (index + 1 == count)
00516 viewFrame.size[_sizeComponent] = bounds.size[_sizeComponent] - viewFrame.origin[_originComponent];
00517 else if (totalSizableSpace && isSizable && lastSizableIndex === index)
00518 viewFrame.size[_sizeComponent] = MAX(0, ROUND([view frame].size[_sizeComponent] + remainingFlexibleSpace))
00519 else if (isSizable || !totalSizableSpace)
00520 {
00521 viewFrame.size[_sizeComponent] = MAX(0, ROUND(ratio * [view frame].size[_sizeComponent]));
00522 remainingFlexibleSpace -= (viewFrame.size[_sizeComponent] - [view frame].size[_sizeComponent]);
00523 }
00524 else if (totalSizableSpace && !isSizable)
00525 viewFrame.size[_sizeComponent] = [view frame].size[_sizeComponent];
00526
00527 bounds.origin[_originComponent] += viewFrame.size[_sizeComponent] + dividerThickness;
00528
00529 [view setFrame:viewFrame];
00530 }
00531
00532 [self _postNotificationDidResize];
00533 }
00534
00535 - (void)setDelegate:(id)delegate
00536 {
00537 if ([_delegate respondsToSelector:@selector(splitViewDidResizeSubviews:)])
00538 [[CPNotificationCenter defaultCenter] removeObserver:_delegate name:CPSplitViewDidResizeSubviewsNotification object:self];
00539 if ([_delegate respondsToSelector:@selector(splitViewWillResizeSubviews:)])
00540 [[CPNotificationCenter defaultCenter] removeObserver:_delegate name:CPSplitViewWillResizeSubviewsNotification object:self];
00541
00542 _delegate = delegate;
00543
00544 if ([_delegate respondsToSelector:@selector(splitViewDidResizeSubviews:)])
00545 [[CPNotificationCenter defaultCenter] addObserver:_delegate
00546 selector:@selector(splitViewDidResizeSubviews:)
00547 name:CPSplitViewDidResizeSubviewsNotification
00548 object:self];
00549 if ([_delegate respondsToSelector:@selector(splitViewWillResizeSubviews:)])
00550 [[CPNotificationCenter defaultCenter] addObserver:_delegate
00551 selector:@selector(splitViewWillResizeSubviews:)
00552 name:CPSplitViewWillResizeSubviewsNotification
00553 object:self];
00554 }
00555
00556 - (void)_postNotificationWillResize
00557 {
00558 [[CPNotificationCenter defaultCenter] postNotificationName:CPSplitViewWillResizeSubviewsNotification object:self];
00559 }
00560
00561 - (void)_postNotificationDidResize
00562 {
00563 [[CPNotificationCenter defaultCenter] postNotificationName:CPSplitViewDidResizeSubviewsNotification object:self];
00564 }
00565
00566 @end
00567
00568 var CPSplitViewDelegateKey = "CPSplitViewDelegateKey",
00569 CPSplitViewIsVerticalKey = "CPSplitViewIsVerticalKey",
00570 CPSplitViewIsPaneSplitterKey = "CPSplitViewIsPaneSplitterKey";
00571
00572 @implementation CPSplitView (CPCoding)
00573
00574
00575
00576
00577
00578 - (id)initWithCoder:(CPCoder)aCoder
00579 {
00580 self = [super initWithCoder:aCoder];
00581
00582 if (self)
00583 {
00584 _currentDivider = CPNotFound;
00585
00586 _DOMDividerElements = [];
00587
00588 _delegate = [aCoder decodeObjectForKey:CPSplitViewDelegateKey];;
00589
00590 _isPaneSplitter = [aCoder decodeBoolForKey:CPSplitViewIsPaneSplitterKey];
00591 [self _setVertical:[aCoder decodeBoolForKey:CPSplitViewIsVerticalKey]];
00592 }
00593
00594 return self;
00595 }
00596
00597
00598
00599
00600
00601 - (void)encodeWithCoder:(CPCoder)aCoder
00602 {
00603 [super encodeWithCoder:aCoder];
00604
00605 [aCoder encodeConditionalObject:_delegate forKey:CPSplitViewDelegateKey];
00606
00607 [aCoder encodeBool:_isVertical forKey:CPSplitViewIsVerticalKey];
00608 [aCoder encodeBool:_isPaneSplitter forKey:CPSplitViewIsPaneSplitterKey];
00609 }
00610
00611 @end