mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 21:17:03 +00:00
[#50 state:resolved responsible:tlrobinson] [#51 state:resolved responsible:tlrobinson]
425 lines
15 KiB
Plaintext
425 lines
15 KiB
Plaintext
/*
|
|
* 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;
|
|
CPString _dividerImagePath;
|
|
int _drawingDivider;
|
|
}
|
|
|
|
/*
|
|
@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";
|
|
_dividerImagePath = [self isVertical] ? [CPSplitViewVerticalImage filename] : [CPSplitViewHorizontalImage filename];
|
|
|
|
[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
|
|
{
|
|
var count = [_subviews count];
|
|
for (var _drawingDivider = 0; _drawingDivider < count-1; _drawingDivider++)
|
|
{
|
|
[self drawDividerInRect:[self rectOfDividerAtIndex:_drawingDivider]];
|
|
}
|
|
}
|
|
|
|
- (void)drawDividerInRect:(CGRect)aRect
|
|
{
|
|
#if PLATFORM(DOM)
|
|
if (!_DOMDividerElements[_drawingDivider])
|
|
{
|
|
_DOMDividerElements[_drawingDivider] = document.createElement("div");
|
|
_DOMDividerElements[_drawingDivider].style.cursor = "move";
|
|
_DOMDividerElements[_drawingDivider].style.position = "absolute";
|
|
_DOMDividerElements[_drawingDivider].style.backgroundRepeat = "repeat";
|
|
CPDOMDisplayServerAppendChild(_DOMElement, _DOMDividerElements[_drawingDivider]);
|
|
}
|
|
|
|
CPDOMDisplayServerSetStyleLeftTop(_DOMDividerElements[_drawingDivider], NULL, _CGRectGetMinX(aRect), _CGRectGetMinY(aRect));
|
|
CPDOMDisplayServerSetStyleSize(_DOMDividerElements[_drawingDivider], _CGRectGetWidth(aRect), _CGRectGetHeight(aRect));
|
|
|
|
_DOMDividerElements[_drawingDivider].style.backgroundImage = "url('"+_dividerImagePath+"')";
|
|
#endif
|
|
}
|
|
|
|
- (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));
|
|
}
|
|
|
|
|
|
/*
|
|
Tracks the divider.
|
|
@param anEvent the input event
|
|
*/
|
|
- (void)trackDivider:(CPEvent)anEvent
|
|
{
|
|
var type = [anEvent type];
|
|
|
|
if (type == CPLeftMouseUp)
|
|
{
|
|
if (_currentDivider != CPNotFound)
|
|
{
|
|
_currentDivider = CPNotFound;
|
|
[self _postNotificationDidResize];
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (type == CPLeftMouseDown)
|
|
{
|
|
var location = [anEvent locationInWindow],
|
|
point = [self convertPoint:location fromView:nil];
|
|
|
|
_currentDivider = CPNotFound;
|
|
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 ([anEvent 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];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (type == CPLeftMouseDragged)
|
|
{
|
|
if (_currentDivider != CPNotFound)
|
|
{
|
|
var location = [anEvent locationInWindow],
|
|
point = [self convertPoint:location fromView:nil];
|
|
|
|
[self setPosition:(point[_originComponent] + _initialOffset) ofDividerAtIndex:_currentDivider];
|
|
}
|
|
}
|
|
|
|
[CPApp setTarget:self selector:@selector(trackDivider:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
|
|
}
|
|
|
|
- (void)mouseDown:(CPEvent)anEvent
|
|
{
|
|
[self trackDivider:anEvent];
|
|
}
|
|
|
|
- (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)];
|