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 PLATFORM(DOM)
00151 _DOMDividerElements = [];
00152 #endif
00153
00154 [self setNeedsDisplay:YES];
00155 }
00156
00157 - (void)didAddSubview:(CPView)aSubview
00158 {
00159 _needsResizeSubviews = YES;
00160
00161 }
00162
00163 - (BOOL)isSubviewCollapsed:(CPView)subview
00164 {
00165 return [subview frame].size[_sizeComponent] < 1 ? YES : NO;
00166 }
00167
00168 - (CGRect)rectOfDividerAtIndex:(int)aDivider
00169 {
00170 var frame = [_subviews[aDivider] frame],
00171 rect = CGRectMakeZero();
00172
00173 rect.size = [self frame].size;
00174
00175 rect.size[_sizeComponent] = [self dividerThickness];
00176 rect.origin[_originComponent] = frame.origin[_originComponent] + frame.size[_sizeComponent];
00177
00178 return rect;
00179 }
00180
00181 - (CGRect)effectiveRectOfDividerAtIndex:(int)aDivider
00182 {
00183 var realRect = [self rectOfDividerAtIndex:aDivider];
00184
00185 var padding = 2;
00186
00187 realRect.size[_sizeComponent] += padding * 2;
00188 realRect.origin[_originComponent] -= padding;
00189
00190 return realRect;
00191 }
00192
00193 - (void)drawRect:(CGRect)rect
00194 {
00195 var count = [_subviews count] - 1;
00196
00197 while ((count--) > 0)
00198 {
00199 _drawingDivider = count;
00200 [self drawDividerInRect:[self rectOfDividerAtIndex:count]];
00201 }
00202 }
00203
00204 - (void)drawDividerInRect:(CGRect)aRect
00205 {
00206 #if PLATFORM(DOM)
00207 if (!_DOMDividerElements[_drawingDivider])
00208 {
00209 _DOMDividerElements[_drawingDivider] = document.createElement("div");
00210 _DOMDividerElements[_drawingDivider].style.cursor = "move";
00211 _DOMDividerElements[_drawingDivider].style.position = "absolute";
00212 _DOMDividerElements[_drawingDivider].style.backgroundRepeat = "repeat";
00213
00214 CPDOMDisplayServerAppendChild(_DOMElement, _DOMDividerElements[_drawingDivider]);
00215
00216 if (_isPaneSplitter)
00217 {
00218 _DOMDividerElements[_drawingDivider].style.backgroundColor = "#A5A5A5";
00219 _DOMDividerElements[_drawingDivider].style.backgroundImage = "";
00220 }
00221 else
00222 {
00223 _DOMDividerElements[_drawingDivider].style.backgroundColor = "";
00224 _DOMDividerElements[_drawingDivider].style.backgroundImage = "url('"+_dividerImagePath+"')";
00225 }
00226 }
00227
00228 CPDOMDisplayServerSetStyleLeftTop(_DOMDividerElements[_drawingDivider], NULL, _CGRectGetMinX(aRect), _CGRectGetMinY(aRect));
00229 CPDOMDisplayServerSetStyleSize(_DOMDividerElements[_drawingDivider], _CGRectGetWidth(aRect), _CGRectGetHeight(aRect));
00230 #endif
00231 }
00232
00233 - (void)viewWillDraw
00234 {
00235 [self _adjustSubviewsWithCalculatedSize];
00236 }
00237
00238 - (void)_adjustSubviewsWithCalculatedSize
00239 {
00240 if (!_needsResizeSubviews)
00241 return;
00242
00243 _needsResizeSubviews = NO;
00244
00245 var subviews = [self subviews],
00246 count = subviews.length,
00247 oldSize = CGSizeMakeZero();
00248
00249 if ([self isVertical])
00250 {
00251 oldSize.width += [self dividerThickness] * (count - 1);
00252 oldSize.height = CGRectGetHeight([self frame]);
00253 }
00254 else
00255 {
00256 oldSize.width = CGRectGetWidth([self frame]);
00257 oldSize.height += [self dividerThickness] * (count - 1);
00258 }
00259
00260 while (count--)
00261 oldSize[_sizeComponent] += [subviews[count] frame].size[_sizeComponent];
00262
00263 [self resizeSubviewsWithOldSize:oldSize];
00264 }
00265
00266 - (BOOL)cursorAtPoint:(CPPoint)aPoint hitDividerAtIndex:(int)anIndex
00267 {
00268 var frame = [_subviews[anIndex] frame],
00269 startPosition = frame.origin[_originComponent] + frame.size[_sizeComponent],
00270 effectiveRect = [self effectiveRectOfDividerAtIndex:anIndex],
00271 additionalRect = null;
00272
00273 if ([_delegate respondsToSelector:@selector(splitView:effectiveRect:forDrawnRect:ofDividerAtIndex:)])
00274 effectiveRect = [_delegate splitView:self effectiveRect:effectiveRect forDrawnRect:effectiveRect ofDividerAtIndex:anIndex];
00275
00276 if ([_delegate respondsToSelector:@selector(splitView:additionalEffectiveRectOfDividerAtIndex:)])
00277 additionalRect = [_delegate splitView:self additionalEffectiveRectOfDividerAtIndex:anIndex];
00278
00279 return CGRectContainsPoint(effectiveRect, aPoint) || (additionalRect && CGRectContainsPoint(additionalRect, aPoint));
00280 }
00281
00282 - (CPView)hitTest:(CGPoint)aPoint
00283 {
00284 if ([self isHidden] || ![self hitTests] || !CGRectContainsPoint([self frame], aPoint))
00285 return nil;
00286
00287 var point = [self convertPoint:aPoint fromView:[self superview]];
00288
00289 var count = [_subviews count] - 1;
00290 for (var i = 0; i < count; i++)
00291 {
00292 if ([self cursorAtPoint:point hitDividerAtIndex:i])
00293 return self;
00294 }
00295
00296 return [super hitTest:aPoint];
00297 }
00298
00299
00300
00301
00302
00303 - (void)trackDivider:(CPEvent)anEvent
00304 {
00305 var type = [anEvent type];
00306
00307 if (type == CPLeftMouseUp)
00308 {
00309 if (_currentDivider != CPNotFound)
00310 {
00311 _currentDivider = CPNotFound;
00312 [self _postNotificationDidResize];
00313 }
00314
00315 return;
00316 }
00317
00318 if (type == CPLeftMouseDown)
00319 {
00320 var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
00321
00322 _currentDivider = CPNotFound;
00323 var count = [_subviews count] - 1;
00324 for (var i = 0; i < count; i++)
00325 {
00326 var frame = [_subviews[i] frame],
00327 startPosition = frame.origin[_originComponent] + frame.size[_sizeComponent];
00328
00329 if ([self cursorAtPoint:point hitDividerAtIndex:i])
00330 {
00331 if ([anEvent clickCount] == 2 &&
00332 [_delegate respondsToSelector:@selector(splitView:canCollapseSubview:)] &&
00333 [_delegate respondsToSelector:@selector(splitView:shouldCollapseSubview:forDoubleClickOnDividerAtIndex:)])
00334 {
00335 var minPosition = [self minPossiblePositionOfDividerAtIndex:i],
00336 maxPosition = [self maxPossiblePositionOfDividerAtIndex:i];
00337
00338 if ([_delegate splitView:self canCollapseSubview:_subviews[i]] && [_delegate splitView:self shouldCollapseSubview:_subviews[i] forDoubleClickOnDividerAtIndex:i])
00339 {
00340 if ([self isSubviewCollapsed:_subviews[i]])
00341 [self setPosition:(minPosition + (maxPosition - minPosition) / 2) ofDividerAtIndex:i];
00342 else
00343 [self setPosition:minPosition ofDividerAtIndex:i];
00344 }
00345 else if ([_delegate splitView:self canCollapseSubview:_subviews[i+1]] && [_delegate splitView:self shouldCollapseSubview:_subviews[i+1] forDoubleClickOnDividerAtIndex:i])
00346 {
00347 if ([self isSubviewCollapsed:_subviews[i+1]])
00348 [self setPosition:(minPosition + (maxPosition - minPosition) / 2) ofDividerAtIndex:i];
00349 else
00350 [self setPosition:maxPosition ofDividerAtIndex:i];
00351 }
00352 }
00353 else
00354 {
00355 _currentDivider = i;
00356 _initialOffset = startPosition - point[_originComponent];
00357
00358 [self _postNotificationWillResize];
00359 }
00360 }
00361 }
00362 }
00363
00364 else if (type == CPLeftMouseDragged && _currentDivider != CPNotFound)
00365 {
00366 var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
00367
00368 [self setPosition:(point[_originComponent] + _initialOffset) ofDividerAtIndex:_currentDivider];
00369 }
00370
00371 [CPApp setTarget:self selector:@selector(trackDivider:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
00372 }
00373
00374 - (void)mouseDown:(CPEvent)anEvent
00375 {
00376
00377 [self trackDivider:anEvent];
00378 }
00379
00380 - (float)maxPossiblePositionOfDividerAtIndex:(int)dividerIndex
00381 {
00382 var frame = [_subviews[dividerIndex + 1] frame];
00383
00384 if (dividerIndex + 1 < [_subviews count] - 1)
00385 return frame.origin[_originComponent] + frame.size[_sizeComponent] - [self dividerThickness];
00386 else
00387 return [self frame].size[_sizeComponent] - [self dividerThickness];
00388 }
00389
00390 - (float)minPossiblePositionOfDividerAtIndex:(int)dividerIndex
00391 {
00392 if (dividerIndex > 0)
00393 {
00394 var frame = [_subviews[dividerIndex - 1] frame];
00395
00396 return frame.origin[_originComponent] + frame.size[_sizeComponent] + [self dividerThickness];
00397 }
00398 else
00399 return 0;
00400 }
00401
00402 - (void)setPosition:(float)position ofDividerAtIndex:(int)dividerIndex
00403 {
00404 [self _adjustSubviewsWithCalculatedSize];
00405
00406
00407 if ([_delegate respondsToSelector:@selector(splitView:constrainSplitPosition:ofSubviewAt:)])
00408 position = [_delegate splitView:self constrainSplitPosition:position ofSubviewAt:dividerIndex];
00409
00410 var proposedMax = [self maxPossiblePositionOfDividerAtIndex:dividerIndex],
00411 proposedMin = [self minPossiblePositionOfDividerAtIndex:dividerIndex],
00412 actualMax = proposedMax,
00413 actualMin = proposedMin;
00414
00415 if([_delegate respondsToSelector:@selector(splitView:constrainMinCoordinate:ofSubviewAt:)])
00416 actualMin = [_delegate splitView:self constrainMinCoordinate:proposedMin ofSubviewAt:dividerIndex];
00417
00418 if([_delegate respondsToSelector:@selector(splitView:constrainMaxCoordinate:ofSubviewAt:)])
00419 actualMax = [_delegate splitView:self constrainMaxCoordinate:proposedMax ofSubviewAt:dividerIndex];
00420
00421 var frame = [self frame],
00422 viewA = _subviews[dividerIndex],
00423 frameA = [viewA frame],
00424 viewB = _subviews[dividerIndex + 1],
00425 frameB = [viewB frame];
00426
00427 var realPosition = MAX(MIN(position, actualMax), actualMin);
00428
00429 if (position < proposedMin + (actualMin - proposedMin) / 2)
00430 if ([_delegate respondsToSelector:@selector(splitView:canCollapseSubview:)])
00431 if ([_delegate splitView:self canCollapseSubview:viewA])
00432 realPosition = proposedMin;
00433
00434 frameA.size[_sizeComponent] = realPosition - frameA.origin[_originComponent];
00435 [_subviews[dividerIndex] setFrame:frameA];
00436
00437 frameB.size[_sizeComponent] = frameB.origin[_originComponent] + frameB.size[_sizeComponent] - realPosition - [self dividerThickness];
00438 frameB.origin[_originComponent] = realPosition + [self dividerThickness];
00439 [_subviews[dividerIndex + 1] setFrame:frameB];
00440
00441 [self setNeedsDisplay:YES];
00442 }
00443
00444 - (void)setFrameSize:(CGSize)aSize
00445 {
00446 [self _adjustSubviewsWithCalculatedSize];
00447
00448 [super setFrameSize:aSize];
00449
00450 [self setNeedsDisplay:YES];
00451 }
00452
00453 - (void)resizeSubviewsWithOldSize:(CPSize)oldSize
00454 {
00455 if ([_delegate respondsToSelector:@selector(splitView:resizeSubviewsWithOldSize:)])
00456 {
00457 [_delegate splitView:self resizeSubviewsWithOldSize:oldSize];
00458 return;
00459 }
00460
00461 [self _postNotificationWillResize];
00462
00463 var index = 0,
00464 count = [_subviews count],
00465 bounds = [self bounds],
00466 dividerThickness = [self dividerThickness],
00467 totalDividers = count - 1,
00468 totalSizableSpace = 0,
00469 nonSizableSpace = 0,
00470 lastSizableIndex = -1,
00471 totalSizablePanes = 0,
00472 isVertical = [self isVertical];
00473
00474 for (index = 0; index < count; ++index)
00475 {
00476 var view = _subviews[index],
00477 isSizable = isVertical ? [view autoresizingMask] & CPViewWidthSizable : [view autoresizingMask] & CPViewHeightSizable;
00478
00479 if (isSizable)
00480 {
00481 totalSizableSpace += [view frame].size[_sizeComponent];
00482 lastSizableIndex = index;
00483 totalSizablePanes++;
00484 }
00485 }
00486
00487 if (totalSizablePanes === count)
00488 totalSizableSpace = 0;
00489
00490 var nonSizableSpace = totalSizableSpace ? bounds.size[_sizeComponent] - totalSizableSpace : 0,
00491 ratio = (bounds.size[_sizeComponent] - totalDividers*dividerThickness - nonSizableSpace) / (oldSize[_sizeComponent]- totalDividers*dividerThickness - nonSizableSpace),
00492 remainingFlexibleSpace = bounds.size[_sizeComponent] - oldSize[_sizeComponent];
00493
00494 for (index = 0; index < count; ++index)
00495 {
00496 var view = _subviews[index],
00497 viewFrame = CGRectMakeCopy(bounds),
00498 isSizable = isVertical ? [view autoresizingMask] & CPViewWidthSizable : [view autoresizingMask] & CPViewHeightSizable;
00499
00500 if (index + 1 == count)
00501 viewFrame.size[_sizeComponent] = bounds.size[_sizeComponent] - viewFrame.origin[_originComponent];
00502 else if (totalSizableSpace && isSizable && lastSizableIndex === index)
00503 viewFrame.size[_sizeComponent] = MAX(0, ROUND([view frame].size[_sizeComponent] + remainingFlexibleSpace))
00504 else if (isSizable || !totalSizableSpace)
00505 {
00506 viewFrame.size[_sizeComponent] = MAX(0, ROUND(ratio * [view frame].size[_sizeComponent]));
00507 remainingFlexibleSpace -= (viewFrame.size[_sizeComponent] - [view frame].size[_sizeComponent]);
00508 }
00509 else if (totalSizableSpace && !isSizable)
00510 viewFrame.size[_sizeComponent] = [view frame].size[_sizeComponent];
00511 else
00512 alert("SHOULD NEVER GET HERE");
00513
00514 bounds.origin[_originComponent] += viewFrame.size[_sizeComponent] + dividerThickness;
00515
00516 [view setFrame:viewFrame];
00517 }
00518
00519 [self _postNotificationDidResize];
00520 }
00521
00522 - (void)setDelegate:(id)delegate
00523 {
00524 if ([_delegate respondsToSelector:@selector(splitViewDidResizeSubviews:)])
00525 [[CPNotificationCenter defaultCenter] removeObserver:_delegate name:CPSplitViewDidResizeSubviewsNotification object:self];
00526 if ([_delegate respondsToSelector:@selector(splitViewWillResizeSubviews:)])
00527 [[CPNotificationCenter defaultCenter] removeObserver:_delegate name:CPSplitViewWillResizeSubviewsNotification object:self];
00528
00529 _delegate = delegate;
00530
00531 if ([_delegate respondsToSelector:@selector(splitViewDidResizeSubviews:)])
00532 [[CPNotificationCenter defaultCenter] addObserver:_delegate
00533 selector:@selector(splitViewDidResizeSubviews:)
00534 name:CPSplitViewDidResizeSubviewsNotification
00535 object:self];
00536 if ([_delegate respondsToSelector:@selector(splitViewWillResizeSubviews:)])
00537 [[CPNotificationCenter defaultCenter] addObserver:_delegate
00538 selector:@selector(splitViewWillResizeSubviews:)
00539 name:CPSplitViewWillResizeSubviewsNotification
00540 object:self];
00541 }
00542
00543 - (void)_postNotificationWillResize
00544 {
00545 [[CPNotificationCenter defaultCenter] postNotificationName:CPSplitViewWillResizeSubviewsNotification object:self];
00546 }
00547
00548 - (void)_postNotificationDidResize
00549 {
00550 [[CPNotificationCenter defaultCenter] postNotificationName:CPSplitViewDidResizeSubviewsNotification object:self];
00551 }
00552
00553 @end
00554
00555 var CPSplitViewDelegateKey = "CPSplitViewDelegateKey",
00556 CPSplitViewIsVerticalKey = "CPSplitViewIsVerticalKey",
00557 CPSplitViewIsPaneSplitterKey = "CPSplitViewIsPaneSplitterKey";
00558
00559 @implementation CPSplitView (CPCoding)
00560
00561
00562
00563
00564
00565 - (id)initWithCoder:(CPCoder)aCoder
00566 {
00567 self = [super initWithCoder:aCoder];
00568
00569 if (self)
00570 {
00571 _currentDivider = CPNotFound;
00572
00573 _DOMDividerElements = [];
00574
00575 _delegate = [aCoder decodeObjectForKey:CPSplitViewDelegateKey];;
00576
00577 _isPaneSplitter = [aCoder decodeBoolForKey:CPSplitViewIsPaneSplitterKey];
00578 [self _setVertical:[aCoder decodeBoolForKey:CPSplitViewIsVerticalKey]];
00579 }
00580
00581 return self;
00582 }
00583
00584
00585
00586
00587
00588 - (void)encodeWithCoder:(CPCoder)aCoder
00589 {
00590 [super encodeWithCoder:aCoder];
00591
00592 [aCoder encodeConditionalObject:_delegate forKey:CPSplitViewDelegateKey];
00593
00594 [aCoder encodeBool:_isVertical forKey:CPSplitViewIsVerticalKey];
00595 [aCoder encodeBool:_isPaneSplitter forKey:CPSplitViewIsPaneSplitterKey];
00596 }
00597
00598 @end