From dbc0999f66969d22422ee198dd55702f68b5effd Mon Sep 17 00:00:00 2001 From: Tom Robinson Date: Wed, 17 Sep 2008 22:37:59 -0700 Subject: [PATCH] CPSplitView initial commit Reviewed by Francisco [#46 state:resolved responsible:tlrobinson] --- AppKit/CPSplitView.j | 407 ++++++++++++++++++ .../CPSplitView/CPSplitViewHorizontal.png | Bin 0 -> 144 bytes .../CPSplitView/CPSplitViewVertical.png | Bin 0 -> 133 bytes 3 files changed, 407 insertions(+) create mode 100644 AppKit/CPSplitView.j create mode 100644 AppKit/Resources/CPSplitView/CPSplitViewHorizontal.png create mode 100644 AppKit/Resources/CPSplitView/CPSplitViewVertical.png diff --git a/AppKit/CPSplitView.j b/AppKit/CPSplitView.j new file mode 100644 index 000000000..97bff90a7 --- /dev/null +++ b/AppKit/CPSplitView.j @@ -0,0 +1,407 @@ +/* + * CPSplitView.j + * AppKit + * + * Created by Thomas Robinson. + * Copyright 2008, 280 North, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +import "CPImage.j" +import "CPView.j" + +#include "CoreGraphics/CGGeometry.h" +#include "Platform/Platform.h" +#include "Platform/DOM/CPDOMDisplayServer.h" + +CPSplitViewDidResizeSubviewsNotification = @"CPSplitViewDidResizeSubviewsNotification"; +CPSplitViewWillResizeSubviewsNotification = @"CPSplitViewWillResizeSubviewsNotification"; + +var CPSplitViewHorizontalImage = nil, + CPSplitViewVerticalImage = nil; + +@implementation CPSplitView : CPView +{ + id _delegate; + BOOL _isVertical; + BOOL _isPaneSplitter; + + int _currentDivider; + float _initialOffset; + + CPString _originComponent; + CPString _sizeComponent; + + CPArray _DOMDividerElements; +} + +/* + @ignore +*/ +//+ (void)initialize +//{ +// if (self != [CPSplitView class]) +// return; +// +// var bundle = [CPBundle bundleForClass:self]; +// +// CPSplitViewHorizontalImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPSplitView/CPSplitViewHorizontal.png"] size:CPSizeMake(5.0, 10.0)]; +// CPSplitViewVerticalImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPSplitView/CPSplitViewVertical.png"] size:CPSizeMake(10.0, 5.0)]; +//} + +- (id)initWithFrame:(CGRect)aFrame +{ + if (self = [super initWithFrame:aFrame]) + { + _currentDivider = CPNotFound; + + _DOMDividerElements = []; + + [self setVertical:YES]; + [self setIsPaneSplitter:YES]; + } + + return self; +} + +- (float)dividerThickness +{ + return 10.0; +} + +- (BOOL)isVertical +{ + return _isVertical; +} +- (void)setVertical:(BOOL)flag +{ + _isVertical = flag; + + _originComponent = [self isVertical] ? "x" : "y"; + _sizeComponent = [self isVertical] ? "width" : "height"; + + [self adjustSubviews]; +} + +- (BOOL)isPaneSplitter +{ + return _isPaneSplitter; +} +- (void)setIsPaneSplitter:(BOOL)flag +{ + _isPaneSplitter = flag; +} + +- (void)didAddSubview:(CPView)subview +{ + [self adjustSubviews]; +} + +- (void)adjustSubviews +{ + var frame = [self frame], + dividerThickness = [self dividerThickness]; + + [self _postNotificationWillResize]; + + var eachSize = (frame.size[_sizeComponent] - dividerThickness * (_subviews.length - 1)) / _subviews.length; + + for (var i = 0; i < [_subviews count]; i++) + { + if ([self isVertical]) + [_subviews[i] setFrame:CGRectMake(Math.round((eachSize + dividerThickness) * i), 0, Math.round(eachSize), frame.size.height)]; + else + [_subviews[i] setFrame:CGRectMake(0, Math.round((eachSize + dividerThickness) * i), frame.size.width, Math.round(eachSize))]; + } + + [self setNeedsDisplay:YES]; + + [self _postNotificationDidResize]; +} + +- (BOOL)isSubviewCollapsed:(CPView)subview +{ + return [subview frame].size[_sizeComponent] < 1 ? YES : NO; +} + +- (CGRect)rectOfDividerAtIndex:(int)aDivider +{ + var frame = [_subviews[aDivider] frame]; + + var rect = CGRectMakeZero(); + rect.size = CPSizeCreateCopy([self frame].size); + + rect.size[_sizeComponent] = [self dividerThickness]; + rect.origin[_originComponent] = frame.origin[_originComponent] + frame.size[_sizeComponent]; + + return rect; +} + +- (CGRect)effectiveRectOfDividerAtIndex:(int)aDivider +{ + var realRect = [self rectOfDividerAtIndex:aDivider]; + + realRect.size[_sizeComponent] += 4; + realRect.origin[_originComponent] -= 2; + + return realRect; +} + +- (void)drawRect:(CGRect)rect +{ +#if PLATFORM(DOM) + var dividerPath = [self isVertical] ? [CPSplitViewVerticalImage filename] : [CPSplitViewHorizontalImage filename]; + + var count = [_subviews count]; + for (var i = 0; i < count; i++) + { + if (!_DOMDividerElements[i]) + { + _DOMDividerElements[i] = document.createElement("div"); + _DOMDividerElements[i].style.cursor = "move"; + _DOMDividerElements[i].style.position = "absolute"; + _DOMDividerElements[i].style.backgroundRepeat = "repeat"; + CPDOMDisplayServerAppendChild(_DOMElement, _DOMDividerElements[i]); + } + + var frame = [self rectOfDividerAtIndex:i]; + CPDOMDisplayServerSetStyleLeftTop(_DOMDividerElements[i], NULL, _CGRectGetMinX(frame), _CGRectGetMinY(frame)); + CPDOMDisplayServerSetStyleSize(_DOMDividerElements[i], _CGRectGetWidth(frame), _CGRectGetHeight(frame)); + + _DOMDividerElements[i].style.backgroundImage = "url('"+dividerPath+"')"; + } +#endif +} + +- (void)drawDividerInRect:(CGRect)aRect +{ + // TODO: figure out a way to "draw" dividers using DOM element +} + +- (BOOL)cursorAtPoint:(CPPoint)aPoint hitDividerAtIndex:(int)anIndex +{ + var frame = [_subviews[anIndex] frame], + startPosition = frame.origin[_originComponent] + frame.size[_sizeComponent], + effectiveRect = [self effectiveRectOfDividerAtIndex:anIndex], + additionalRect = null; + + if ([_delegate respondsToSelector:@selector(splitView:effectiveRect:forDrawnRect:ofDividerAtIndex:)]) + effectiveRect = [_delegate splitView:self effectiveRect:effectiveRect forDrawnRect:effectiveRect ofDividerAtIndex:anIndex]; + + if ([_delegate respondsToSelector:@selector(splitView:additionalEffectiveRectOfDividerAtIndex:)]) + additionalRect = [_delegate splitView:self additionalEffectiveRectOfDividerAtIndex:anIndex]; + + return CGRectContainsPoint(effectiveRect, aPoint) || (additionalRect && CGRectContainsPoint(additionalRect, aPoint)); +} + +- (void)mouseDown:(CPEvent)theEvent +{ + var location = [theEvent locationInWindow], + point = [self convertPoint:location fromView:nil]; + + for (var i = 0; i < [_subviews count] - 1; i++) + { + var frame = [_subviews[i] frame], + startPosition = frame.origin[_originComponent] + frame.size[_sizeComponent]; + + if ([self cursorAtPoint:point hitDividerAtIndex:i]) + { + if ([theEvent clickCount] == 2 && + [_delegate respondsToSelector:@selector(splitView:canCollapseSubview:)] && + [_delegate respondsToSelector:@selector(splitView:shouldCollapseSubview:forDoubleClickOnDividerAtIndex:)]) + { + var minPosition = [self minPossiblePositionOfDividerAtIndex:i], + maxPosition = [self maxPossiblePositionOfDividerAtIndex:i]; + + if ([_delegate splitView:self canCollapseSubview:_subviews[i]] && [_delegate splitView:self shouldCollapseSubview:_subviews[i] forDoubleClickOnDividerAtIndex:i]) + { + if ([self isSubviewCollapsed:_subviews[i]]) + [self setPosition:(minPosition + (maxPosition - minPosition) / 2) ofDividerAtIndex:i]; + else + [self setPosition:minPosition ofDividerAtIndex:i]; + } + else if ([_delegate splitView:self canCollapseSubview:_subviews[i+1]] && [_delegate splitView:self shouldCollapseSubview:_subviews[i+1] forDoubleClickOnDividerAtIndex:i]) + { + if ([self isSubviewCollapsed:_subviews[i+1]]) + [self setPosition:(minPosition + (maxPosition - minPosition) / 2) ofDividerAtIndex:i]; + else + [self setPosition:maxPosition ofDividerAtIndex:i]; + } + } + else + { + _currentDivider = i; + _initialOffset = startPosition - point[_originComponent]; + + [self _postNotificationWillResize]; + } + + return; + } + } + _currentDivider = CPNotFound; +} + +- (void)mouseDragged:(CPEvent)theEvent +{ + if (_currentDivider != CPNotFound) + { + var location = [theEvent locationInWindow], + point = [self convertPoint:location fromView:nil]; + + [self setPosition:(point[_originComponent] + _initialOffset) ofDividerAtIndex:_currentDivider]; + } +} + +- (void)mouseUp:(CPEvent)theEvent +{ + if (_currentDivider != CPNotFound) + { + _currentDivider = CPNotFound; + [self _postNotificationDidResize]; + } +} + +- (float)maxPossiblePositionOfDividerAtIndex:(int)dividerIndex +{ + var frame = [_subviews[dividerIndex + 1] frame]; + + if (dividerIndex + 1 < [_subviews count] - 1) + return frame.origin[_originComponent] + frame.size[_sizeComponent] - [self dividerThickness]; + else + return [self frame].size[_sizeComponent] - [self dividerThickness]; +} + +- (float)minPossiblePositionOfDividerAtIndex:(int)dividerIndex +{ + var frame = [_subviews[dividerIndex - 1] frame]; + + if (dividerIndex > 0) + return frame.origin[_originComponent] + frame.size[_sizeComponent] + [self dividerThickness]; + else + return 0; +} + +- (void)setPosition:(float)position ofDividerAtIndex:(int)dividerIndex +{ + // not sure where this should override other positions? + if ([_delegate respondsToSelector:@selector(splitView:constrainSplitPosition:ofSubviewAt:)]) + position = [_delegate splitView:self constrainSplitPosition:position ofSubviewAt:dividerIndex]; + + var proposedMax = [self maxPossiblePositionOfDividerAtIndex:dividerIndex], + proposedMin = [self minPossiblePositionOfDividerAtIndex:dividerIndex], + actualMax = proposedMax, + actualMin = proposedMin; + + if([_delegate respondsToSelector:@selector(splitView:constrainMinCoordinate:ofSubviewAt:)]) + actualMin = [_delegate splitView:self constrainMinCoordinate:proposedMin ofSubviewAt:dividerIndex]; + + if([_delegate respondsToSelector:@selector(splitView:constrainMaxCoordinate:ofSubviewAt:)]) + actualMax = [_delegate splitView:self constrainMaxCoordinate:proposedMax ofSubviewAt:dividerIndex]; + + var frame = [self frame], + viewA = _subviews[dividerIndex], + frameA = CGRectCreateCopy([viewA frame]), + viewB = _subviews[dividerIndex+1], + frameB = CGRectCreateCopy([viewB frame]); + + var realPosition = MAX(MIN(position, actualMax), actualMin); + + if (position < proposedMin + (actualMin - proposedMin) / 2) + if ([_delegate respondsToSelector:@selector(splitView:canCollapseSubview:)]) + if ([_delegate splitView:self canCollapseSubview:viewA]) + realPosition = proposedMin; + + frameA.size[_sizeComponent] = realPosition - frameA.origin[_originComponent]; + [_subviews[dividerIndex] setFrame:frameA]; + + frameB.size[_sizeComponent] = frameB.origin[_originComponent] + frameB.size[_sizeComponent] - realPosition - [self dividerThickness]; + frameB.origin[_originComponent] = realPosition + [self dividerThickness]; + [_subviews[dividerIndex+1] setFrame:frameB]; + + [self setNeedsDisplay:YES]; +} + +- (void)resizeSubviewsWithOldSize:(CPSize)oldSize +{ + if ([_delegate respondsToSelector:@selector(splitView:resizeSubviewsWithOldSize:)]) + { + [_delegate splitView:self resizeSubviewsWithOldSize:oldSize]; + return; + } + + [self _postNotificationWillResize]; + + var frame = CGRectCreateCopy([self frame]); + + for (var i = 0; i < [_subviews count]; i++) + { + var view = [_subviews objectAtIndex:i], + newFrame = CGRectCreateCopy(frame); + + if(i + 1 == [_subviews count]) + newFrame.size[_sizeComponent] = frame.size[_sizeComponent] - newFrame.origin[_originComponent]; + else + newFrame.size[_sizeComponent] = frame.size[_sizeComponent] * ([view frame].size[_sizeComponent] / oldSize[_sizeComponent]); + + frame.origin[_originComponent] += newFrame.size[_sizeComponent]; + frame.origin[_originComponent] += [self dividerThickness]; + + [view setFrame:newFrame]; + } + + [self setNeedsDisplay:YES]; + + [self _postNotificationDidResize]; +} + +- (void)setDelegate:(id)delegate +{ + if ([_delegate respondsToSelector:@selector(splitViewDidResizeSubviews:)]) + [[CPNotificationCenter defaultCenter] removeObserver:_delegate name:CPSplitViewDidResizeSubviewsNotification object:self]; + if ([_delegate respondsToSelector:@selector(splitViewWillResizeSubviews:)]) + [[CPNotificationCenter defaultCenter] removeObserver:_delegate name:CPSplitViewWillResizeSubviewsNotification object:self]; + + _delegate = delegate; + + if ([_delegate respondsToSelector:@selector(splitViewDidResizeSubviews:)]) + [[CPNotificationCenter defaultCenter] addObserver:_delegate + selector:@selector(splitViewDidResizeSubviews:) + name:CPSplitViewDidResizeSubviewsNotification + object:self]; + if ([_delegate respondsToSelector:@selector(splitViewWillResizeSubviews:)]) + [[CPNotificationCenter defaultCenter] addObserver:_delegate + selector:@selector(splitViewWillResizeSubviews:) + name:CPSplitViewWillResizeSubviewsNotification + object:self]; +} + +- (void)_postNotificationWillResize +{ + [[CPNotificationCenter defaultCenter] postNotificationName:CPSplitViewWillResizeSubviewsNotification object:self]; +} + +- (void)_postNotificationDidResize +{ + [[CPNotificationCenter defaultCenter] postNotificationName:CPSplitViewDidResizeSubviewsNotification object:self]; +} + +@end + +// FIXME: once +initialize is fixed for superclasses, remove this and uncomment +initialize +var bundle = [CPBundle bundleForClass:CPSplitView]; +CPSplitViewHorizontalImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPSplitView/CPSplitViewHorizontal.png"] size:CPSizeMake(5.0, 10.0)]; +CPSplitViewVerticalImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPSplitView/CPSplitViewVertical.png"] size:CPSizeMake(10.0, 5.0)]; diff --git a/AppKit/Resources/CPSplitView/CPSplitViewHorizontal.png b/AppKit/Resources/CPSplitView/CPSplitViewHorizontal.png new file mode 100644 index 0000000000000000000000000000000000000000..e144e9b31ebf6d9c989c2123f0e23fcb4460ff63 GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^%s|Y=!2~4#xfUw`DajJoh?3y^w370~qErUQl>DSr z1<%~X^wgl##FWaylc_d9MMj=3jv*Ddk|KANXfjI}{QLEl`_!+GkJ+bu{rWnbqxSpkC^>bP0l+XkKx*{>j literal 0 HcmV?d00001 diff --git a/AppKit/Resources/CPSplitView/CPSplitViewVertical.png b/AppKit/Resources/CPSplitView/CPSplitViewVertical.png new file mode 100644 index 0000000000000000000000000000000000000000..2dd8973d368e7d8aa7eea810b381607ada1f8dd3 GIT binary patch literal 133 zcmeAS@N?(olHy`uVBq!ia0vp^AT~1x6OdGm=Trhxk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5X)ID7sLn>}1#q26s+1tx2EG%qnY|PEg-O^%kZC&i?BMssL d6WBNm7+TX=zVaHazW~(2;OXk;vd$@?2>=^mBsTy6 literal 0 HcmV?d00001