diff --git a/AppKit/CPCollectionView.j b/AppKit/CPCollectionView.j
index 62069c8e1..f206d1473 100644
--- a/AppKit/CPCollectionView.j
+++ b/AppKit/CPCollectionView.j
@@ -177,7 +177,7 @@
*/
- (BOOL)isFirstResponder
{
- return [[self window] firstResponder] == self;
+ return [[self window] firstResponder] === self;
}
// Setting the Content
diff --git a/AppKit/CPColor.j b/AppKit/CPColor.j
index 23f2415df..636f96a3f 100644
--- a/AppKit/CPColor.j
+++ b/AppKit/CPColor.j
@@ -375,6 +375,16 @@ var cachedBlackColor,
return cachedClearColor;
}
++ (CPColor)alternateSelectedControlColor
+{
+ return [[CPColor alloc] _initWithRGBA:[0.22, 0.46, 0.84, 1.0]];
+}
+
++ (CPColor)secondarySelectedControlColor
+{
+ return [[CPColor alloc] _initWithRGBA:[0.83, 0.83, 0.83, 1.0]];
+}
+
/*!
Creates a color using a tile pattern with \c anImage
@param the image to tile
diff --git a/AppKit/CPScrollView.j b/AppKit/CPScrollView.j
index 49a844ed2..c6703958a 100644
--- a/AppKit/CPScrollView.j
+++ b/AppKit/CPScrollView.j
@@ -38,7 +38,8 @@
@implementation CPScrollView : CPView
{
CPClipView _contentView;
-
+ CPClipView _headerClipView;
+
BOOL _hasVerticalScroller;
BOOL _hasHorizontalScroller;
BOOL _autohidesScrollers;
@@ -57,23 +58,27 @@
- (id)initWithFrame:(CGRect)aFrame
{
self = [super initWithFrame:aFrame];
-
+
if (self)
{
_verticalLineScroll = 10.0;
_verticalPageScroll = 10.0;
-
+
_horizontalLineScroll = 10.0;
_horizontalPageScroll = 10.0;
_contentView = [[CPClipView alloc] initWithFrame:[self bounds]];
-
+
[self addSubview:_contentView];
-
+
+ _headerClipView = [[CPClipView alloc] init];
+
+ [self addSubview:_headerClipView];
+
[self setHasVerticalScroller:YES];
[self setHasHorizontalScroller:YES];
}
-
+
return self;
}
@@ -100,24 +105,24 @@
*/
- (void)setContentView:(CPClipView)aContentView
{
- if (!aContentView)
+ if (_contentView !== aContentView || !aContentView)
return;
-
+
var documentView = [aContentView documentView];
-
+
if (documentView)
[documentView removeFromSuperview];
-
+
[_contentView removeFromSuperview];
-
- var size = [self contentSize];
-
+
_contentView = aContentView;
-
- [_contentView setFrame:CGRectMake(0.0, 0.0, size.width, size.height)];
+
[_contentView setDocumentView:documentView];
[self addSubview:_contentView];
+
+ // This will size the content view appropriately, so no need to size it in this method.
+ [self reflectScrolledClipView:_contentView];
}
/*!
@@ -134,8 +139,15 @@
*/
- (void)setDocumentView:(CPView)aView
{
- [_contentView setDocumentView:aView];
- [self reflectScrolledClipView:_contentView];
+ [_contentView setDocumentView:aView];
+ [_headerClipView setDocumentView:[self _headerView]];
+
+ var cornerView = [self _cornerView];
+
+ if (cornerView)
+ [self addSubview:cornerView];
+
+ [self reflectScrolledClipView:_contentView];
}
/*!
@@ -166,72 +178,98 @@
// [_verticalScroller setEnabled:NO];
// [_horizontalScroller setEnabled:NO];
}
-
+
[_contentView setFrame:[self bounds]];
-
+ [_headerClipView setFrame:_CGRectMakeZero()];
+
--_recursionCount;
-
+
return;
}
- var documentFrame = [documentView frame],
- contentViewFrame = [self bounds],
- scrollPoint = [_contentView bounds].origin,
- difference = _CGSizeMake(CPRectGetWidth(documentFrame) - CPRectGetWidth(contentViewFrame), CPRectGetHeight(documentFrame) - CPRectGetHeight(contentViewFrame)),
- shouldShowVerticalScroller = (!_autohidesScrollers || difference.height > 0.0) && _hasVerticalScroller,
- shouldShowHorizontalScroller = (!_autohidesScrollers || difference.width > 0.0) && _hasHorizontalScroller,
- wasShowingVerticalScroller = ![_verticalScroller isHidden],
- wasShowingHorizontalScroller = ![_horizontalScroller isHidden],
- verticalScrollerWidth = _CGRectGetWidth([_verticalScroller frame]);
- horizontalScrollerHeight = _CGRectGetHeight([_horizontalScroller frame]);
+ var documentFrame = [documentView frame], // the size of the whole document
+ contentFrame = [self bounds], // assume it takes up the entire size of the scrollview (no scrollers)
+ headerClipViewFrame = [self headerClipViewFrame],
+ headerClipViewHeight = _CGRectGetHeight(headerClipViewFrame);
- if (_autohidesScrollers)
- {
- // Check to see if either affected the other!
- if (shouldShowVerticalScroller)
- shouldShowHorizontalScroller = (!_autohidesScrollers || difference.width > -verticalScrollerWidth) && _hasHorizontalScroller;
+ contentFrame.origin.y += headerClipViewHeight;
+ contentFrame.size.height -= headerClipViewHeight;
- if (shouldShowHorizontalScroller)
- shouldShowVerticalScroller = (!_autohidesScrollers || difference.height > -horizontalScrollerHeight) && _hasVerticalScroller;
- }
-
- [_verticalScroller setHidden:!shouldShowVerticalScroller];
- [_verticalScroller setEnabled:difference.height > 0.0];
-
- [_horizontalScroller setHidden:!shouldShowHorizontalScroller];
- [_horizontalScroller setEnabled:difference.width > 0.0];
+ var difference = _CGSizeMake(_CGRectGetWidth(documentFrame) - _CGRectGetWidth(contentFrame), _CGRectGetHeight(documentFrame) - _CGRectGetHeight(contentFrame)),
+ verticalScrollerWidth = _CGRectGetWidth([_verticalScroller frame]),
+ horizontalScrollerHeight = _CGRectGetHeight([_horizontalScroller frame]),
+ hasVerticalScroll = difference.height > 0.0,
+ hasHorizontalScroll = difference.width > 0.0,
+ shouldShowVerticalScroller = _hasVerticalScroller && (!_autohidesScrollers || hasVerticalScroll),
+ shouldShowHorizontalScroller = _hasHorizontalScroller && (!_autohidesScrollers || hasHorizontalScroll);
+ // Now we have to account for the shown scrollers affecting the deltas.
if (shouldShowVerticalScroller)
{
- var verticalScrollerHeight = CPRectGetHeight(contentViewFrame);
-
- if (shouldShowHorizontalScroller)
- verticalScrollerHeight -= horizontalScrollerHeight;
-
difference.width += verticalScrollerWidth;
- contentViewFrame.size.width -= verticalScrollerWidth;
-
- [_verticalScroller setFloatValue:(difference.height <= 0.0) ? 0.0 : scrollPoint.y / difference.height
- knobProportion:CPRectGetHeight(contentViewFrame) / CPRectGetHeight(documentFrame)];
- [_verticalScroller setFrame:CPRectMake(CPRectGetMaxX(contentViewFrame), 0.0, verticalScrollerWidth, verticalScrollerHeight)];
+ hasHorizontalScroll = difference.width > 0.0;
+ shouldShowHorizontalScroller = _hasHorizontalScroller && (!_autohidesScrollers || hasHorizontalScroll);
}
- else if (wasShowingVerticalScroller)
- [_verticalScroller setFloatValue:0.0 knobProportion:1.0];
-
+
if (shouldShowHorizontalScroller)
{
difference.height += horizontalScrollerHeight;
- contentViewFrame.size.height -= horizontalScrollerHeight;
-
- [_horizontalScroller setFloatValue:(difference.width <= 0.0) ? 0.0 : scrollPoint.x / difference.width
- knobProportion:CPRectGetWidth(contentViewFrame) / CPRectGetWidth(documentFrame)];
- [_horizontalScroller setFrame:CPRectMake(0.0, CPRectGetMaxY(contentViewFrame), CPRectGetWidth(contentViewFrame), horizontalScrollerHeight)];
+ hasVerticalScroll = difference.height > 0.0;
+ shouldShowVerticalScroller = _hasVerticalScroller && (!_autohidesScrollers || hasVerticalScroll);
+ }
+
+ // We now definitively know which scrollers are shown or not, as well as whether they are showing scroll values.
+ [_verticalScroller setHidden:!shouldShowVerticalScroller];
+ [_verticalScroller setEnabled:hasVerticalScroll];
+
+ [_horizontalScroller setHidden:!shouldShowHorizontalScroller];
+ [_horizontalScroller setEnabled:hasHorizontalScroll];
+
+ // We can thus appropriately account for them changing the content size.
+ if (shouldShowVerticalScroller)
+ contentFrame.size.width -= verticalScrollerWidth;
+
+ if (shouldShowHorizontalScroller)
+ contentFrame.size.height -= horizontalScrollerHeight;
+
+ var scrollPoint = [_contentView bounds].origin,
+ wasShowingVerticalScroller = ![_verticalScroller isHidden],
+ wasShowingHorizontalScroller = ![_horizontalScroller isHidden];
+
+ if (shouldShowVerticalScroller)
+ {
+ var verticalScrollerY = MAX(_CGRectGetHeight([self cornerViewFrame]), headerClipViewHeight),
+ verticalScrollerHeight = _CGRectGetHeight([self bounds]) - verticalScrollerY;
+
+ if (shouldShowHorizontalScroller)
+ verticalScrollerHeight -= horizontalScrollerHeight;
+
+ [_verticalScroller setFloatValue:(difference.height <= 0.0) ? 0.0 : scrollPoint.y / difference.height];
+ [_verticalScroller setKnobProportion:_CGRectGetHeight(contentFrame) / _CGRectGetHeight(documentFrame)];
+ [_verticalScroller setFrame:_CGRectMake(_CGRectGetMaxX(contentFrame), verticalScrollerY, verticalScrollerWidth, verticalScrollerHeight)];
+ }
+ else if (wasShowingVerticalScroller)
+ {
+ [_verticalScroller setFloatValue:0.0];
+ [_verticalScroller setKnobProportion:1.0];
+ }
+
+ if (shouldShowHorizontalScroller)
+ {
+ [_horizontalScroller setFloatValue:(difference.width <= 0.0) ? 0.0 : scrollPoint.x / difference.width];
+ [_horizontalScroller setKnobProportion:_CGRectGetWidth(contentFrame) / _CGRectGetWidth(documentFrame)];
+ [_horizontalScroller setFrame:_CGRectMake(0.0, _CGRectGetMaxY(contentFrame), _CGRectGetWidth(contentFrame), horizontalScrollerHeight)];
}
else if (wasShowingHorizontalScroller)
- [_horizontalScroller setFloatValue:0.0 knobProportion:1.0];
-
- [_contentView setFrame:contentViewFrame];
-
+ {
+ [_horizontalScroller setFloatValue:0.0];
+ [_horizontalScroller setKnobProportion:1.0];
+ }
+
+ [_contentView setFrame:contentFrame];
+ [_headerClipView setFrame:headerClipViewFrame];
+ [[self _cornerView] setFrame:[self cornerViewFrame]];
+
--_recursionCount;
}
@@ -280,7 +318,7 @@
_hasHorizontalScroller = shouldHaveHorizontalScroller;
if (_hasHorizontalScroller && !_horizontalScroller)
- [self setHorizontalScroller:[[CPScroller alloc] initWithFrame:CGRectMake(0.0, 0.0, CPRectGetWidth([self bounds]), [CPScroller scrollerWidth])]];
+ [self setHorizontalScroller:[[CPScroller alloc] initWithFrame:CGRectMake(0.0, 0.0, _CGRectGetWidth([self bounds]), [CPScroller scrollerWidth])]];
else if (!_hasHorizontalScroller && _horizontalScroller)
{
@@ -343,7 +381,7 @@
_hasVerticalScroller = shouldHaveVerticalScroller;
if (_hasVerticalScroller && !_verticalScroller)
- [self setVerticalScroller:[[CPScroller alloc] initWithFrame:CPRectMake(0.0, 0.0, [CPScroller scrollerWidth], CPRectGetHeight([self bounds]))]];
+ [self setVerticalScroller:[[CPScroller alloc] initWithFrame:_CGRectMake(0.0, 0.0, [CPScroller scrollerWidth], _CGRectGetHeight([self bounds]))]];
else if (!_hasVerticalScroller && _verticalScroller)
{
@@ -384,13 +422,56 @@
{
return _autohidesScrollers;
}
-/*
-- (void)setFrameSize:(CPRect)aSize
+
+- (CPView)_headerView
{
- [super setFrameSize:aSize];
+ var documentView = [self documentView];
- [self reflectScrolledClipView:_contentView];
-}*/
+ if ([documentView respondsToSelector:@selector(headerView)])
+ return [documentView headerView];
+
+ return nil;
+}
+
+- (CPView)_cornerView
+{
+ var documentView = [self documentView];
+
+ if ([documentView respondsToSelector:@selector(cornerView)])
+ return [documentView cornerView];
+}
+
+- (CGRect)cornerViewFrame
+{
+ var cornerView = [self _cornerView],
+ cornerBounds = [cornerView bounds],
+ bounds = [self bounds];
+
+ if (!cornerView)
+ return _CGRectMakeZero();
+
+ cornerBounds.origin.x = CGRectGetMaxX(bounds) - CGRectGetWidth(cornerBounds);
+ cornerBounds.origin.y = 0;
+
+ return cornerBounds;
+}
+
+- (CGRect)headerClipViewFrame
+{
+ var headerView = [self _headerView],
+ cornerView = [self _cornerView],
+ bounds = [self bounds];
+
+ if (!headerView)
+ return _CGRectMakeZero();
+
+ bounds.size.height = [headerView bounds].size.height;
+
+ if (cornerView)
+ bounds.size.width -= CGRectGetWidth([cornerView bounds]);
+
+ return bounds;
+}
/* @ignore */
- (void)_verticalScrollerDidScroll:(CPScroller)aScroller
@@ -448,6 +529,7 @@
}
[_contentView scrollToPoint:contentBounds.origin];
+ [_headerClipView scrollToPoint:CGPointMake(contentBounds.origin.x, 0.0)];
}
/*!
@@ -587,6 +669,7 @@
contentBounds.origin.y += [anEvent deltaY] * _verticalLineScroll;
[_contentView scrollToPoint:contentBounds.origin];
+ [_headerClipView scrollToPoint:CGPointMake(contentBounds.origin.x, 0.0)];
}
- (void)keyDown:(CPEvent)anEvent
@@ -625,6 +708,7 @@
}
[_contentView scrollToPoint:contentBounds.origin];
+ [_headerClipView scrollToPoint:CGPointMake(contentBounds.origin, 0)];
}
@end
diff --git a/AppKit/CPScroller.j b/AppKit/CPScroller.j
index 77248fb23..b6664f474 100644
--- a/AppKit/CPScroller.j
+++ b/AppKit/CPScroller.j
@@ -64,9 +64,9 @@ NAMES_FOR_PARTS[CPScrollerKnob] = @"knob";
BOOL _isVertical @accessors(readonly, getter=isVertical);
float _knobProportion;
-
+
CPScrollerPart _hitPart;
-
+
CPScrollerPart _trackingPart;
float _trackingFloatValue;
CGPoint _trackingStartPoint;
@@ -104,11 +104,12 @@ NAMES_FOR_PARTS[CPScrollerKnob] = @"knob";
_controlSize = CPRegularControlSize;
_partRects = [];
- [self setFloatValue:0.0 knobProportion:1.0];
+ [self setFloatValue:0.0];
+ [self setKnobProportion:1.0];
_hitPart = CPScrollerNoPart;
- [self _recalculateIsVertical];
+ [self _calculateIsVertical];
}
return self;
@@ -155,28 +156,17 @@ NAMES_FOR_PARTS[CPScrollerKnob] = @"knob";
return _controlSize;
}
-// Setting the Knob Position
-/*!
- Sets the scroller's knob position (ranges from 0.0 to 1.0).
- @param aValue the knob position (ranges from 0.0 to 1.0)
-*/
-- (void)setFloatValue:(float)aValue
+- (void)setObjectValue:(id)aValue
{
- [super setFloatValue:MIN(1.0, MAX(0.0, aValue))];
-
- [self setNeedsLayout];
+ [super setObjectValue:MIN(1.0, MAX(0.0, +aValue))];
}
-/*!
- Sets the position and proportion of the knob.
- @param aValue the knob position (ranges from 0.0 to 1.0)
- @param aProportion the knob's proportion (ranges from 0.0 to 1.0)
-*/
-- (void)setFloatValue:(float)aValue knobProportion:(float)aProportion
+- (void)setKnobProportion:(float)aProportion
{
_knobProportion = MIN(1.0, MAX(0.0001, aProportion));
- [self setFloatValue:aValue];
+ [self setNeedsDisplay:YES];
+ [self setNeedsLayout];
}
/*!
@@ -533,15 +523,15 @@ NAMES_FOR_PARTS[CPScrollerKnob] = @"knob";
}
-- (void)_recalculateIsVertical
+- (void)_calculateIsVertical
{
// Recalculate isVertical.
var bounds = [self bounds],
width = _CGRectGetWidth(bounds),
height = _CGRectGetHeight(bounds);
-
+
_isVertical = width < height ? 1 : (width > height ? 0 : -1);
-
+
if (_isVertical === 1)
[self setThemeState:CPThemeStateVertical];
else if (_isVertical === 0)
@@ -552,8 +542,6 @@ NAMES_FOR_PARTS[CPScrollerKnob] = @"knob";
{
[super setFrameSize:aSize];
- [self _recalculateIsVertical];
-
[self checkSpaceForParts];
[self setNeedsLayout];
}
@@ -590,18 +578,16 @@ var CPScrollerControlSizeKey = "CPScrollerControlSize",
_controlSize = CPRegularControlSize;
if ([aCoder containsValueForKey:CPScrollerControlSizeKey])
_controlSize = [aCoder decodeIntForKey:CPScrollerControlSizeKey];
-
+
_knobProportion = 1.0;
if ([aCoder containsValueForKey:CPScrollerKnobProportionKey])
_knobProportion = [aCoder decodeFloatForKey:CPScrollerKnobProportionKey];
-
+
_partRects = [];
_hitPart = CPScrollerNoPart;
- [self _recalculateIsVertical];
-// [self checkSpaceForParts];
-// [self setNeedsLayout];
+ [self _calculateIsVertical];
}
return self;
@@ -616,3 +602,18 @@ var CPScrollerControlSizeKey = "CPScrollerControlSize",
}
@end
+
+@implementation CPScroller (Deprecated)
+
+/*!
+ Sets the position and proportion of the knob.
+ @param aValue the knob position (ranges from 0.0 to 1.0)
+ @param aProportion the knob's proportion (ranges from 0.0 to 1.0)
+*/
+- (void)setFloatValue:(float)aValue knobProportion:(float)aProportion
+{
+ [self setFloatValue:aValue];
+ [self setKnobProportion:aProportion];
+}
+
+@end
diff --git a/AppKit/CPTableColumn.j b/AppKit/CPTableColumn.j
index 8d44cc0b2..9da18cfe3 100644
--- a/AppKit/CPTableColumn.j
+++ b/AppKit/CPTableColumn.j
@@ -3,7 +3,7 @@
* AppKit
*
* Created by Francisco Tolmasky.
- * Copyright 2008, 280 North, Inc.
+ * Copyright 2009, 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
@@ -20,12 +20,13 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-@import
Each CPTableColumn object is identified by a CPString, called the column identifier. The reason is that, after a column has been added to a table view, the user might move the columns around, so there is a need to identify the columns regardless of its position in the table. - - @ignore -*/ @implementation CPTableColumn : CPObject { - CPString _identifier; - CPView _headerView; - - CPTableView _tableView; - - float _width; - float _minWidth; - float _maxWidth; - - unsigned _resizingMask; + CPTableView _tableView; + CPView _headerView; + CPView _dataView; + Object _dataViewData; - CPView _dataView; // default data view for this column + float _width; + float _minWidth; + float _maxWidth; - Object _dataViewData; // cache of data view archives (key=data view UID, value=data view archive) - Object _dataViewForView; // mapping from view instances back to their data view prototype (key=view instance UID, value=data view) - Object _purgableInfosForDataView; // (key=data view UID, value=) + id _identifier; + BOOL _isEditable; + CPSortDescriptor _sortDescriptorPrototype; + BOOL _isHidden; + CPString _headerToolTip; } -/*! - Initializes the table column with the specified identifier. - @param anIdentifier the identifier - @return the initialized table column -*/ -- (id)initWithIdentifier:(CPString)anIdentifier +- (id)initWithIdentifier:(id)anIdentifier { self = [super init]; - + if (self) { - [self _init]; - - _identifier = anIdentifier; - - _width = 40.0; - _minWidth = 8.0; - _maxWidth = 1000.0; - - var dataView = [[CPTextField alloc] initWithFrame:CPRectMakeZero()]; - [dataView setValue:[CPColor whiteColor] forThemeAttribute:"text-color" inState:CPThemeStateHighlighted]; - - [self setDataView:dataView]; - - _headerView = [[CPTextField alloc] initWithFrame:CPRectMakeZero()]; - [_headerView setBackgroundColor:[CPColor greenColor]]; + _dataViewData = { }; + + _width = 100.0; + _minWidth = 10.0; + _maxWidth = 1000000.0; + + [self setIdentifier:anIdentifier]; + [self setHeaderView:[CPTextField new]]; + [self setDataView:[CPTextField new]]; } - + return self; } -- (void)_init -{ - _dataViewData = {}; - _dataViewForView = {}; - _purgableInfosForDataView = {}; -} - -/*! - Sets the table column's identifier - @param anIdentifier the new identifier -*/ -- (void)setIdentifier:(CPString)anIdentifier -{ - _identifier = anIdentifier; -} - -/*! - Returns the table column's identifier -*/ -- (CPString)identifier -{ - return _identifier; -} - -// Setting the CPTableView -/*! - Sets the table's view. This is called automatically by Cappuccino. - @param aTableView the new table view -*/ - (void)setTableView:(CPTableView)aTableView { _tableView = aTableView; } -/*! - Returns the column's table view. -*/ - (CPTableView)tableView { return _tableView; } -// Controlling size -/*! - Sets the column's width. - @param aWidth the new column width -*/ - (void)setWidth:(float)aWidth { - _width = aWidth; + aWidth = +aWidth; + + if (_width === aWidth) + return; + + var newWidth = MIN(MAX(aWidth, [self minWidth]), [self maxWidth]); + + if (_width === newWidth) + return; + + var oldWidth = _width; + + _width = newWidth; + + var tableView = [self tableView]; + + if (tableView) + [[CPNotificationCenter defaultCenter] + postNotificationName:CPTableViewColumnDidResizeNotification + object:tableView + userInfo:[CPDictionary dictionaryWithObjects:[self, oldWidth] forKeys:[@"CPTableColumn", "CPOldWidth"]]]; } -/*! - Returns the column's width -*/ - (float)width { return _width; } -/*! - Sets column's minimum width. - @param aWidth the new minimum column width -*/ -- (void)setMinWidth:(float)aWidth +- (void)setMinWidth:(float)aMinWidth { - if (_width < (_minWidth = aWidth)) - [self setWidth:_minWidth]; + aMinWidth = +aMinWidth; + + if (_minWidth === aMinWidth) + return; + + _minWidth = aMinWidth; + + var width = [self width], + newWidth = MAX(width, [self minWidth]); + + if (width !== newWidth) + [self setWidth:newWidth]; } -/*! - The column's minimum width -*/ - (float)minWidth { return _minWidth; } -/*! - Sets the column's maximum width. - @param aWidth the new maximum width -*/ -- (void)setMaxWidth:(float)aWidth +- (void)setMaxWidth:(float)aMaxWidth { - if (_width > (_maxmimumWidth = aWidth)) - [self setWidth:_maxWidth]; + aMaxWidth = +aMaxWidth; + + if (_maxWidth === aMaxWidth) + return; + + _maxWidth = aMaxWidth; + + var width = [self width], + newWidth = MAX(width, [self maxWidth]); + + if (width !== newWidth) + [self setWidth:newWidth]; } -/*! - Sets the resizing mask. The mask is one of: -
-CPTableColumnNoResizing; -CPTableColumnAutoresizingMask; -CPTableColumnUserResizingMask; -- @param aMask the new resizing mask -*/ -- (void)setResizingMask:(unsigned)aMask +- (float)maxWidth { - _resizingMask = aMask; + return _maxWidth; } -/*! - Returns the column's resizing mask. One of: -
-CPTableColumnNoResizing; -CPTableColumnAutoresizingMask; -CPTableColumnUserResizingMask; --*/ -- (unsigned)resizingMask +- (void)setResizingMask:(unsigned)aResizingMask +{ + _resizingMask = aResizingMask; +} + +- (float)resizingMask { return _resizingMask; } -/*! - Resizes the column according to the min, max and set width. -*/ - (void)sizeToFit { - var width = CPRectGetWidth([_headerView frame]); - - if (width < _minWidth) + var width = _CGRectGetWidth([_headerView frame]); + + if (width < [self minWidth]) [self setMinWidth:width]; - else if (width > _maxWidth) + else if (width > [self maxWidth]) [self setMaxWidth:width] - if (_width != width) + if (_width !== width) [self setWidth:width]; } -/*! - Sets whether the column in this data is editable. - @param aFlag \c YES means the column data is editable -*/ -- (void)setEditable:(BOOL)aFlag +//Setting Component Cells +- (void)setHeaderView:(CPView)aView { - _isEditable = aFlag; + if (!aView) + [CPException raise:CPInvalidArgumentException reason:@"Attempt to set nil header view on " + [self description]]; + + _headerView = aView; } -/*! - Returns \c YES if the column data is editable. +- (CPView)headerView +{ + return _headerView; +} + +- (void)setDataView:(CPView)aView +{ + if (_dataView === aView) + return; + + if (_dataView) + _dataViewData[[_dataView UID]] = nil; + + _dataView = aView; + _dataViewData[[aView UID]] = [CPKeyedArchiver archivedDataWithRootObject:aView]; +} + +- (CPView)dataView +{ + return _dataView; +} + +/* + Returns the CPView object used by the CPTableView to draw values for the receiver. + + By default, this method just calls dataView. Subclassers can override if they need to + potentially use different cells for different rows. Subclasses should expect this method + to be invoked with row equal to -1 in cases where no actual row is involved but the table + view needs to get some generic cell info. +*/ +- (id)dataViewForRow:(int)aRowIndex +{ + return [self dataView]; +} + +- (id)_newDataViewForRow:(int)aRowIndex +{ + var dataView = [self dataViewForRow:aRowIndex], + dataViewUID = [dataView UID]; + +var x = [self tableView]._cachedDataViews[dataViewUID]; +if (x && x.length) +return x.pop(); + + // if we haven't cached an archive of the data view, do it now + if (!_dataViewData[dataViewUID]) + _dataViewData[dataViewUID] = [CPKeyedArchiver archivedDataWithRootObject:dataView]; + + // unarchive the data view cache + var newDataView = [CPKeyedUnarchiver unarchiveObjectWithData:_dataViewData[dataViewUID]]; +newDataView.identifier = dataViewUID; + return newDataView; +} + +//Setting the Identifier + +/* + Sets the receiver identifier to anIdentifier. +*/ +- (void)setIdentifier:(id)anIdentifier +{ + _identifier = anIdentifier; +} + +/* + Returns the object used by the data source to identify the attribute corresponding to the receiver. +*/ +- (id)identifier +{ + return _identifier; +} + +//Controlling Editability + +/* + Controls whether the user can edit cells in the receiver by double-clicking them. +*/ +- (void)setEditable:(BOOL)shouldBeEditable +{ + _isEditable = shouldBeEditable; +} + +/* + Returns YES if the user can edit cells associated with the receiver by double-clicking the + column in the NSTableView, NO otherwise. */ - (BOOL)isEditable { return _isEditable; } -//Setting the column header view - -/*! - Sets the view that draws the column's header. - @param aHeaderView the view that will draws the column header -*/ - -- (void)setHeaderView:(CPView)aView +//Sorting +- (void)setSortDescriptorPrototype:(CPSortDescriptor)aSortDescriptor { - _headerView = aView; + _sortDescriptorPrototype = aSortDescriptor; } -/*! - Return the view that draws the column's header -*/ -- (CPView)headerView +- (CPSortDescriptor)sortDescriptorPrototype { - return _headerView; + return _sortDescriptorPrototype; } -/*! - Sets the data cell that draws rows in this column. -*/ -- (void)setDataCell:(CPView
-- (int)numberOfRowsInTableView:(CPTableView)aTableView -- (id)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)aTableColumn row:(int)rowIndex -- @param aDataSource the object with the table data - @throws CPInternalInconsistencyException if \c aDataSource doesn't implement all the required methods -*/ - (void)setDataSource:(id)aDataSource { - if (![aDataSource respondsToSelector:@selector(numberOfRowsInTableView:)]) - [CPException raise:CPInternalInconsistencyException reason:"Data source doesn't support 'numberOfRowsInTableView:'"]; - if (![aDataSource respondsToSelector:@selector(tableView:objectValueForTableColumn:row:)]) - [CPException raise:CPInternalInconsistencyException reason:"Data source doesn't support 'tableView:objectValueForTableColumn:row:'"]; + if (_dataSource === aDataSource) + return; _dataSource = aDataSource; - + _implementedDataSourceMethods = 0; + + if (!_dataSource) + return; + + if (![_dataSource respondsToSelector:@selector(numberOfRowsInTableView:)]) + [CPException raise:CPInternalInconsistencyException + reason:[aDataSource description] + " does not implement numberOfRowsInTableView:."]; + + if (![_dataSource respondsToSelector:@selector(tableView:objectValueForTableColumn:row:)]) + [CPException raise:CPInternalInconsistencyException + reason:[aDataSource description] + " does not implement tableView:objectValueForTableColumn:row:"]; + + if ([_dataSource respondsToSelector:@selector(tableView:setObjectValue:forTableColumn:row:)]) + _implementedDataSourceMethods |= CPTableViewDataSource_tableView_setObjectValue_forTableColumn_row_; + + if ([_dataSource respondsToSelector:@selector(tableView:setObjectValue:forTableColumn:row:)]) + _implementedDataSourceMethods |= CPTableViewDataSource_tableView_acceptDrop_row_dropOperation_; + + if ([_dataSource respondsToSelector:@selector(tableView:namesOfPromisedFilesDroppedAtDestination:forDraggedRowsWithIndexes:)]) + _implementedDataSourceMethods |= CPTableViewDataSource_tableView_namesOfPromisedFilesDroppedAtDestination_forDraggedRowsWithIndexes_; + + if ([_dataSource respondsToSelector:@selector(tableView:validateDrop:proposedRow:proposedDropOperation:)]) + _implementedDataSourceMethods |= CPTableViewDataSource_tableView_validateDrop_proposedRow_proposedDropOperation_; + + if ([_dataSource respondsToSelector:@selector(tableView:writeRowsWithIndexes:toPasteboard:)]) + _implementedDataSourceMethods |= CPTableViewDataSource_tableView_writeRowsWithIndexes_toPasteboard_; + [self reloadData]; } -/* - Returns the object that has access to the table data -*/ - (id)dataSource { return _dataSource; } +//Loading Data + +- (void)reloadData +{ + if (!_dataSource) + return; + + _objectValues = { }; + + [self noteNumberOfRowsChanged]; + [self _sizeToParent]; + + [self setNeedsLayout]; + [self setNeedsDisplay:YES]; +} + +//Target-action Behavior + +- (void)setDoubleAction:(SEL)anAction +{ + _doubleAction = anAction; +} + +- (SEL)doubleAction +{ + return _doubleAction; +} + +/* + * - clickedColumn + * - clickedRow +*/ +//Configuring Behavior + +- (void)setAllowsColumnReordering:(BOOL)shouldAllowColumnReordering +{ + _allowsColumnReordering = !!shouldAllowColumnReordering; +} + +- (BOOL)allowsColumnReordering +{ + return _allowsColumnReordering; +} + +- (void)setAllowsColumnResizing:(BOOL)shouldAllowColumnResizing +{ + _allowsColumnResizing = !!shouldAllowColumnResizing; +} + +- (BOOL)allowsColumnResizing +{ + return _allowsColumnResizing; +} + +- (void)setAllowsMultipleSelection:(BOOL)shouldAllowMultipleSelection +{ + _allowsMultipleSelection = !!shouldAllowMultipleSelection; +} + +- (BOOL)allowsMultipleSelection +{ + return _allowsMultipleSelection; +} + +- (void)setAllowsEmptySelection:(BOOL)shouldAllowEmptySelection +{ + _allowsEmptySelection = !!shouldAllowEmptySelection; +} + +- (BOOL)allowsEmptySelection +{ + return _allowsEmptySelection; +} + +- (void)setAllowsColumnSelection:(BOOL)shouldAllowColumnSelection +{ + _allowsColumnSelection = !!shouldAllowColumnSelection; +} + +- (BOOL)allowsColumnSelection +{ + return _allowsColumnSelection; +} + +//Setting Display Attributes + +- (void)setIntercellSpacing:(CGSize)aSize +{ + if (_CGSizeEqualToSize(_intercellSpacing, aSize)) + return; + + _intercellSpacing = _CGSizeMakeCopy(aSize); + + [self setNeedsLayout]; +} + +- (void)setThemeState:(int)astae +{ +} + +- (CGSize)intercellSpacing +{ + return _CGSizeMakeCopy(_intercellSpacing); +} + +- (void)setRowHeight:(unsigned)aRowHeight +{ + aRowHeight = +aRowHeight; + + if (_rowHeight === aRowHeight) + return; + + _rowHeight = MAX(0.0, aRowHeight); + + [self setNeedsLayout]; +} + +- (unsigned)rowHeight +{ + return _rowHeight; +} + +- (void)setUsesAlternatingRowBackgroundColors:(BOOL)shouldUseAlternatingRowBackgroundColors +{ + // TODO:need to look at how one actually sets the alternating row, a tip at: + // http://forums.macnn.com/79/developer-center/228347/nstableview-alternating-row-colors/ + // otherwise this may not be feasible or may introduce an additional change req'd in CP + // we'd probably need to iterate through rowId % 2 == 0 and setBackgroundColor with + // whatever the alternating row color is. + _usesAlternatingRowBackgroundColors = shouldUseAlternatingRowBackgroundColors; +} + +- (BOOL)usesAlternatingRowBackgroundColors +{ + return _usesAlternatingRowBackgroundColors; +} + +- (void)setAlternatingRowBackgroundColors:(CPArray)alternatingRowBackgroundColors +{ + if ([_alternatingRowBackgroundColors isEqual:alternatingRowBackgroundColors]) + return; + + _alternatingRowBackgroundColors = alternatingRowBackgroundColors; + + [self setNeedsDisplay:YES]; +} + +- (CPArray)alternatingRowBackgroundColors +{ + return _alternatingRowBackgroundColors; +} + +- (unsigned)selectionHighlightStyle +{ + return _selectionHighlightMask; +} + +- (void)setSelectionHighlightStyle:(unsigned)aSelectionHighlightStyle +{ + _selectionHighlightMask = aSelectionHighlightStyle; +} + +/* + * - indicatorImageInTableColumn: + * - setIndicatorImage:inTableColumn: +*/ + +- (void)setGridColor:(CPColor)aColor +{ + if (_gridColor === aColor) + return; + + _gridColor = aColor; + + [self setNeedsDisplay:YES]; +} + +- (CPColor)gridColor +{ + return _gridColor; +} + +- (void)setGridStyleMask:(unsigned)aGrideStyleMask +{ + if (_gridStyleMask === aGrideStyleMask) + return; + + _gridStyleMask = aGrideStyleMask + + [self setNeedsDisplay:YES]; +} + +- (unsigned)gridStyleMask +{ + return _gridStyleMask; +} + +//Column Management + +- (void)addTableColumn:(CPTableColumn)aTableColumn +{ + [_tableColumns addObject:aTableColumn]; + [aTableColumn setTableView:self]; + + if (_dirtyTableColumnRangeIndex < 0) + _dirtyTableColumnRangeIndex = NUMBER_OF_COLUMNS() - 1; + else + _dirtyTableColumnRangeIndex = MIN(NUMBER_OF_COLUMNS() - 1, _dirtyTableColumnRangeIndex); + + [self setNeedsLayout]; +} + +- (void)removeTableColumn:(CPTableColumn)aTableColumn +{ + if ([aTableColumn tableView] !== self) + return; + + var index = [_tableColumns indeOfObjectIdenticalTo:aTableColumn]; + + if (index === CPNotFound) + return; + + [aTableColumn setTableView:nil]; + [_tableColumns removeObjectAtIndex:index]; + + var tableColumnUID = [aTableColumn UID]; + + if (_objectValues[tableColumnUID]) + _objectValues[tableColumnUID] = nil; + + if (_dirtyTableColumnRangeIndex < 0) + _dirtyTableColumnRangeIndex = index; + else + _dirtyTableColumnRangeIndex = MIN(index, _dirtyTableColumnRangeIndex); + + [self setNeedsLayout]; +} + +- (void)moveColumn:(unsigned)fromIndex toColumn:(unsigned)toIndex +{ + fromIndex = +fromIndex; + toIndex = +toIndex; + + if (fromIndex === toIndex) + return; + + if (_dirtyTableColumnRangeIndex < 0) + _dirtyTableColumnRangeIndex = MIN(fromIndex, toIndex); + else + _dirtyTableColumnRangeIndex = MIN(fromIndex, toIndex, _dirtyTableColumnRangeIndex); + + if (toIndex > fromIndex) + --toIndex; + + var tableColumn = _tableColumns[fromIndex]; + + [_tableColumns removeObjectAtIndex:fromIndex]; + [_tableColumns insertObject:tableColumn atIndex:toIndex]; + + [self setNeedsLayout]; +} + +- (CPArray)tableColumns +{ + return _tableColumns; +} + +- (CPInteger)columnWithIdentifier:(CPString)anIdentifier +{ + var index = 0, + count = NUMBER_OF_COLUMNS(); + + for (; index < count; ++index) + if ([_tableColumns identifier] === anIdentifier) + return index; + + return CPNotFound; +} + +- (CPTableColumn)tableColumnWithIdentifier:(CPString)anIdentifier +{ + var index = [self columnWithIdentifier:anIdentifier]; + + if (index === CPNotFound) + return nil; + + return _tableColumns[index]; +} + +//Selecting Columns and Rows +- (void)selectColumnIndexes:(CPIndexSet)columns byExtendingSelection:(BOOL)shouldExtendSelection +{ + // We deselect all columns when selecting rows. + _selectedRowIndexes = [CPIndexSet indexSet]; + + if (shouldExtendSelection) + [_selectedColumnIndexes addIndexes:columns]; + else + _selectedColumnIndexes = [columns copy]; + + [self setNeedsLayout]; +} + +- (void)selectRowIndexes:(CPIndexSet)rows byExtendingSelection:(BOOL)shouldExtendSelection +{ + // We deselect all rows when selecting columns. + _selectedColumnIndexes = [CPIndexSet indexSet]; + + if (shouldExtendSelection) + [_selectedRowIndexes addIndexes:rows]; + else + _selectedRowIndexes = [rows copy]; + + [self setNeedsLayout]; +} + +- (CPIndexSet)selectedColumnIndexes +{ + return _selectedColumnIndexes; +} + +- (void)selectedRowIndexes +{ + return _selectedRowIndexes; +} + +- (void)deselectColumn:(CPInteger)aColumn +{ + [_selectedColumnIndexes removeIndex:aColumn]; +} + +- (void)deselectRow:(CPInteger)aRow +{ + [_selectedRowIndexes removeIndex:aRow]; +} + +- (CPInteger)numberOfSelectedColumns +{ + return [_selectedColumnIndexes count]; +} + +- (CPInteger)numberOfSelectedRows +{ + return [_selectedRowIndexes count]; +} + +/* +- (CPInteger)selectedColumn + * - selectedRow +*/ + +- (BOOL)isColumnSelected:(CPInteger)aColumn +{ + return [_selectedColumnIndexes containsIndex:aColumn]; +} + +- (BOOL)isRowSelected:(CPInteger)aRow +{ + return [_selectedRowIndexes containsIndex:aRow]; +} +/* +- (void)selectAll: + * - deselectAll: + * - allowsTypeSelect + * - setAllowsTypeSelect: +*/ +//Table Dimensions + +- (int)numberOfColumns +{ + return NUMBER_OF_COLUMNS(); +} + +/* + Returns the number of rows in the receiver. +*/ +- (int)numberOfRows +{ + if (!_dataSource) + return 0; + + return [_dataSource numberOfRowsInTableView:self]; +} + +//Displaying Cell +/* + * - preparedCellAtColumn:row: +*/ +//Editing Cells +/* + * - editColumn:row:withEvent:select: + * - editedColumn + * - editedRow +*/ +//Setting Auxiliary Views +/* + * - setHeaderView: + * - headerView + * - setCornerView: + * - cornerView +*/ + +- (CPView)cornerView +{ + return _cornerView; +} + +- (void)setCornerView:(CPView)aView +{ + _cornerView = aView; +} + +- (CPView)headerView +{ + return _headerView; +} + +- (void)setHeaderView:(CPView)aHeaderView +{ + _headerView = aHeaderView; + [_headerView setTableView:self]; +} + +//Layout Support + +// Complexity: +// O(Columns) +- (void)_recalculateTableColumnRanges +{ + if (_dirtyTableColumnRangeIndex < 0) + return; + + var index = _dirtyTableColumnRangeIndex, + count = NUMBER_OF_COLUMNS(), + x = index === 0 ? 0.0 : CPMaxRange(_tableColumnRanges[index - 1]); + + for (; index < count; ++index) + { + var tableColumn = _tableColumns[index]; + + if ([tableColumn isHidden]) + _tableColumnRanges[index] = CPMakeRange(x, 0.0); + + else + { + var width = [_tableColumns[index] width]; + + _tableColumnRanges[index] = CPMakeRange(x, width); + + x += width; + } + } + + _tableColumnRanges.length = count; + _dirtyTableColumnRangeIndex = CPNotFound; +} + +// Complexity: +// O(1) +- (CGRect)rectOfColumn:(CPInteger)aColumnIndex +{ + aColumnIndex = +aColumnIndex; + + if (aColumnIndex < 0 || aColumnIndex >= NUMBER_OF_COLUMNS()) + return _CGRectMakeZero(); + + if (_dirtyTableColumnRangeIndex !== CPNotFound) + [self _recalculateTableColumnRanges]; + + var range = _tableColumnRanges[aColumnIndex]; + + return _CGRectMake(range.location, 0.0, range.length, CGRectGetHeight([self bounds])); +} + +- (CGRect)rectOfRow:(CPInteger)aRowIndex +{ + if (NO) + return NULL; + + // FIXME: WRONG: ASK TABLE COLUMN RANGE + return _CGRectMake(0.0, (aRowIndex * (_rowHeight + _intercellSpacing.height)), _CGRectGetWidth([self bounds]), _rowHeight); +} + +// Complexity: +// O(1) +- (CPRange)rowsInRect:(CGRect)aRect +{ + var bounds = nil, + firstRow = [self rowAtPoint:aRect.origin], + lastRow = [self rowAtPoint:_CGPointMake(0.0, _CGRectGetMaxY(aRect))]; + + if (firstRow < 0) + { + bounds = [self bounds]; + + if (_CGRectGetMinY(aRect) < _CGRectGetMinY(bounds)) + firstRow = 0; + else + firstRow = _numberOfRows - 1; + } + + if (lastRow < 0) + { + if (!bounds) + bounds = [self bounds]; + + if (_CGRectGetMaxY(aRect) < _CGRectGetMinY(bounds)) + lastRow = 0; + else + lastRow = _numberOfRows - 1; + } + + return CPMakeRange(firstRow, lastRow - firstRow + 1); +} + +// Complexity: +// O(lg Columns) if table view contains no hidden columns +// O(Columns) if table view contains hidden columns +- (CPIndexSet)columnIndexesInRect:(CGRect)aRect +{ + var column = MAX(0, [self columnAtPoint:_CGPointMake(aRect.origin.x, 0.0)]), + lastColumn = [self columnAtPoint:_CGPointMake(_CGRectGetMaxX(aRect), 0.0)]; + + if (lastColumn === CPNotFound) + lastColumn = NUMBER_OF_COLUMNS() - 1; + + // Don't bother doing the expensive removal of hidden indexes if we have no hidden columns. + if (_numberOfHiddenColumns <= 0) + return [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(column, lastColumn - column + 1)]; + + // + var indexSet = [CPIndexSet indexSet]; + + for (; column <= lastColumn; ++column) + { + var tableColumn = _tableColumns[column]; + + if (![tableColumn isHidden]) + [indexSet addIndex:column]; + } + + return indexSet; +} + +// Complexity: +// O(lg Columns) if table view contains now hidden columns +// O(Columns) if table view contains hidden columns +- (CPInteger)columnAtPoint:(CGPoint)aPoint +{ + var bounds = [self bounds]; + + if (!_CGRectContainsPoint(bounds, aPoint)) + return CPNotFound; + + if (_dirtyTableColumnRangeIndex !== CPNotFound) + [self _recalculateTableColumnRanges]; + + var x = aPoint.x, + low = 0, + high = _tableColumnRanges.length - 1; + + while (low <= high) + { + var middle = FLOOR(low + (high - low) / 2), + range = _tableColumnRanges[middle]; + + if (x < range.location) + high = middle - 1; + + else if (x >= CPMaxRange(range)) + low = middle + 1; + + else + { + var numberOfColumns = _tableColumnRanges.length; + + while (middle < numberOfColumns && [_tableColumns[middle] isHidden]) + ++middle; + + if (middle < numberOfColumns) + return middle; + + return CPNotFound; + } + } + + return CPNotFound; +} + +- (CPInteger)rowAtPoint:(CGPoint)aPoint +{ + var y = aPoint.y; + + if (NO) + { + } + + var row = FLOOR(y / (_rowHeight + _intercellSpacing.height)); + + if (row >= _numberOfRows) + return -1; + + return row; +} + +- (CGRect)frameOfDataViewAtColumn:(CPInteger)aColumnIndex row:(CPInteger)aRowIndex +{ + var tableColumnRange = _tableColumns[aColumnIndex], + rectOfRow = [self rectOfRow:aRowIndex]; + + return _CGRectMake(tableColumnRange.location, _CGRectGetMinY(rectOfRow), tableColumnRange.length, _CGRectGetHeight(rectOfRow)); +} +/* + * - columnAutoresizingStyle + * - setColumnAutoresizingStyle: +*/ +- (void)sizeLastColumnToFit +{ + var superview = [self superview]; + + if (!superview) + return; + + var superviewSize = [superview bounds].size; + + if (_dirtyTableColumnRangeIndex !== CPNotFound) + [self _recalculateTableColumnRanges]; + + var count = NUMBER_OF_COLUMNS(); + + while (count-- && [_tableColumns[count] isHidden]) ; + + if (count >= 0) + [_tableColumns[count] setWidth:MAX(0.0, superviewSize.width - _CGRectGetMinX([self rectOfColumn:count]))]; + + [self setNeedsLayout]; +} + +- (void)noteNumberOfRowsChanged +{ + _numberOfRows = [_dataSource numberOfRowsInTableView:self]; + + [self setNeedsLayout]; +} +/* + * - tile + * - sizeToFit + * - noteHeightOfRowsWithIndexesChanged: +*/ +//Scrolling +/* + * - scrollRowToVisible: + * - scrollColumnToVisible: +*/ +//Persistence +/* + * - autosaveName + * - autosaveTableColumns + * - setAutosaveName: + * - setAutosaveTableColumns: +*/ + +- (void)_sizeToParent +{ + var superviewSize = [[self superview] bounds].size; + + if (_dirtyTableColumnRangeIndex !== CPNotFound) + [self _recalculateTableColumnRanges]; + + if (_tableColumnRanges.length > 0) + var naturalWidth = CPMaxRange([_tableColumnRanges lastObject]); + + else + var naturalWidth = 0.0; + + [self setFrameSize:_CGSizeMake( MAX(superviewSize.width, naturalWidth), + MAX(superviewSize.height, (_rowHeight + _intercellSpacing.height) * _numberOfRows))]; +} + +//Setting the Delegate:(id)aDelegate + +- (void)setDelegate:(id)aDelegate +{ + if (_delegate === aDelegate) + return; + + var defaultCenter = [CPNotificationCenter defaultCenter]; + + if (_delegate) + { + if ([_delegate respondsToSelector:@selector(tableViewColumnDidMove:)]) + [defaultCenter + removeObserver:_delegate + name:CPTableViewColumnDidMoveNotification + object:self]; + + if ([_delegate respondsToSelector:@selector(tableViewColumnDidResize:)]) + [defaultCenter + removeObserver:_delegate + name:CPTableViewColumnDidResizeNotification + object:self]; + + if ([_delegate respondsToSelector:@selector(tableViewSelectionDidChange:)]) + [defaultCenter + removeObserver:_delegate + name:CPTableViewSelectionDidChangeNotification + object:self]; + + if ([_delegate respondsToSelector:@selector(tableViewSelectionIsChanging:)]) + [defaultCenter + removeObserver:_delegate + name:CPTableViewSelectionIsChangingNotification + object:self]; + } + + _delegate = aDelegate; + _implementedDelegateMethods = 0; + + if ([_delegate respondsToSelector:@selector(selectionShouldChangeInTableView:)]) + _implementedDelegateMethods |= CPTableViewDelegate_selectionShouldChangeInTableView_; + + if ([_delegate respondsToSelector:@selector(tableView:dataViewForTableColumn:row:)]) + _implementedDelegateMethods |= CPTableViewDelegate_tableView_dataViewForTableColumn_row_; + + if ([_delegate respondsToSelector:@selector(tableView:didClickTableColumn:)]) + _implementedDelegateMethods |= CPTableViewDelegate_tableView_didClickTableColumn_; + + if ([_delegate respondsToSelector:@selector(tableView:didDragTableColumn:)]) + _implementedDelegateMethods |= CPTableViewDelegate_tableView_didDragTableColumn_; + + if ([_delegate respondsToSelector:@selector(tableView:heightOfRow:)]) + _implementedDelegateMethods |= CPTableViewDelegate_tableView_heightOfRow_; + + if ([_delegate respondsToSelector:@selector(tableView:isGroupRow:)]) + _implementedDelegateMethods |= CPTableViewDelegate_tableView_isGroupRow_; + + if ([_delegate respondsToSelector:@selector(tableView:mouseDownInHeaderOfTableColumn:)]) + _implementedDelegateMethods |= CPTableViewDelegate_tableView_mouseDownInHeaderOfTableColumn_; + + if ([_delegate respondsToSelector:@selector(tableView:nextTypeSelectMatchFromRow:toRow:forString:)]) + _implementedDelegateMethods |= CPTableViewDelegate_tableView_nextTypeSelectMatchFromRow_toRow_forString_; + + if ([_delegate respondsToSelector:@selector(tableView:selectionIndexesForProposedSelection:)]) + _implementedDelegateMethods |= CPTableViewDelegate_tableView_selectionIndexesForProposedSelection_; + + if ([_delegate respondsToSelector:@selector(tableView:shouldEditTableColumn:row:)]) + _implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldEditTableColumn_row_; + + if ([_delegate respondsToSelector:@selector(tableView:shouldSelectRow:)]) + _implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldSelectRow_; + + if ([_delegate respondsToSelector:@selector(tableView:shouldSelectTableColumn:)]) + _implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldSelectTableColumn_; + + if ([_delegate respondsToSelector:@selector(tableView:shouldShowViewExpansionForTableColumn:row:)]) + _implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldShowViewExpansionForTableColumn_row_; + + if ([_delegate respondsToSelector:@selector(tableView:shouldTrackView:forTableColumn:row:)]) + _implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldTrackView_forTableColumn_row_; + + if ([_delegate respondsToSelector:@selector(tableView:shouldTypeSelectForEvent:withCurrentSearchString:)]) + _implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldTypeSelectForEvent_withCurrentSearchString_; + + if ([_delegate respondsToSelector:@selector(tableView:toolTipForView:rect:tableColumn:row:mouseLocation:)]) + _implementedDelegateMethods |= CPTableViewDelegate_tableView_toolTipForView_rect_tableColumn_row_mouseLocation_; + + if ([_delegate respondsToSelector:@selector(tableView:typeSelectStringForTableColumn:row:)]) + _implementedDelegateMethods |= CPTableViewDelegate_tableView_typeSelectStringForTableColumn_row_; + + if ([_delegate respondsToSelector:@selector(tableView:willDisplayView:forTableColumn:row:)]) + _implementedDelegateMethods |= CPTableViewDelegate_tableView_willDisplayView_forTableColumn_row_; + + if ([_delegate respondsToSelector:@selector(tableViewColumnDidMove:)]) + [defaultCenter + addObserver:_delegate + selector:@selector(tableViewColumnDidMove:) + name:CPTableViewColumnDidMoveNotification + object:self]; + + if ([_delegate respondsToSelector:@selector(tableViewColumnDidResize:)]) + [defaultCenter + addObserver:_delegate + selector:@selector(tableViewColumnDidMove:) + name:CPTableViewColumnDidResizeNotification + object:self]; + + if ([_delegate respondsToSelector:@selector(tableViewSelectionDidChange:)]) + [defaultCenter + addObserver:_delegate + selector:@selector(tableViewSelectionDidChange:) + name:CPTableViewSelectionDidChangeNotification + object:self]; + + if ([_delegate respondsToSelector:@selector(tableViewSelectionIsChanging:)]) + [defaultCenter + addObserver:_delegate + selector:@selector(tableViewSelectionIsChanging:) + name:CPTableViewSelectionIsChangingNotification + object:self]; +} - (id)delegate { return _delegate; } - -/*! - Sets the delegate for the tableview. -*/ -- (void)setDelegate:(id)aDelegate +//Highlightable Column Headers +/* +- (CPTableColumn)highlightedTableColumn { - if (_delegate === aDelegate) - return; - - var notificationCenter = [CPNotificationCenter defaultCenter]; - - if ([_delegate respondsToSelector:@selector(tableViewColumnDidMove:)]) - [notificationCenter removeObserver:_delegate name:CPTableViewColumnDidMoveNotification object:self]; - if ([_delegate respondsToSelector:@selector(tableViewColumnDidResize:)]) - [notificationCenter removeObserver:_delegate name:CPTableViewColumnDidResizeNotification object:self]; - if ([_delegate respondsToSelector:@selector(tableViewSelectionDidChange:)]) - [notificationCenter removeObserver:_delegate name:CPTableViewSelectionDidChangeNotification object:self]; - if ([_delegate respondsToSelector:@selector(tableViewSelectionIsChanging:)]) - [notificationCenter removeObserver:_delegate name:CPTableViewSelectionIsChangingNotification object:self]; - - _delegate = aDelegate; - - if ([_delegate respondsToSelector:@selector(tableViewColumnDidMove:)]) - [notificationCenter addObserver:_delegate selector:@selector(tableViewColumnDidMove:) name:CPTableViewColumnDidMoveNotification object:self]; - if ([_delegate respondsToSelector:@selector(tableViewColumnDidResize:)]) - [notificationCenter addObserver:_delegate selector:@selector(tableViewColumnDidResize:) name:CPTableViewColumnDidResizeNotification object:self]; - if ([_delegate respondsToSelector:@selector(tableViewSelectionDidChange:)]) - [notificationCenter addObserver:_delegate selector:@selector(tableViewSelectionDidChange:) name:CPTableViewSelectionDidChangeNotification object:self]; - if ([_delegate respondsToSelector:@selector(tableViewSelectionIsChanging:)]) - [notificationCenter addObserver:_delegate selector:@selector(tableViewSelectionIsChanging:) name:CPTableViewSelectionIsChangingNotification object:self]; - _delegateSelectorsCache = 0; +} - if ([_delegate respondsToSelector:@selector(tableView:willDisplayCell:forTableColumn:row:)]) - _delegateSelectorsCache |= _CPTableViewWillDisplayCellSelector; - if ([_delegate respondsToSelector:@selector(tableView:shouldSelectRow:)]) - _delegateSelectorsCache |= _CPTableViewShouldSelectRowSelector; - if ([_delegate respondsToSelector:@selector(tableView:shouldSelectTableColumn:)]) - _delegateSelectorsCache |= _CPTableViewShouldSelectTableColumnSelector; - if ([_delegate respondsToSelector:@selector(selectionShouldChangeInTableView:)]) - _delegateSelectorsCache |= _CPTableViewSelectionShouldChangeSelector; - if ([_delegate respondsToSelector:@selector(tableView:shouldEditTableColumn:row:)]) - _delegateSelectorsCache |= _CPTableViewShouldEditTableColumnSelector; - if ([_delegate respondsToSelector:@selector(tableView:selectionIndexesForProposedSelection:)]) - _delegateSelectorsCache |= _CPTableViewSelectionIndexesForProposedSelectionSelector; - if ([_delegate respondsToSelector:@selector(tableView:heightOfRow:)]) + * - setHighlightedTableColumn: +*/ +//Dragging +/* + * - dragImageForRowsWithIndexes:tableColumns:event:offset: + * - canDragRowsWithIndexes:atPoint: + * - setDraggingSourceOperationMask:forLocal: + * - setDropRow:dropOperation: + * - setVerticalMotionCanBeginDrag: + * - verticalMotionCanBeginDrag +*/ +//Sorting +/* + * - setSortDescriptors: + * - sortDescriptors +*/ + +//Text Delegate Methods +/* + * - textShouldBeginEditing: + * - textDidBeginEditing: + * - textDidChange: + * - textShouldEndEditing: + * - textDidEndEditing: +*/ + +- (id)_objectValueForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex +{ + var tableColumnUID = [aTableColumn UID], + tableColumnObjectValues = _objectValues[tableColumnUID]; + + if (!tableColumnObjectValues) { - _delegateSelectorsCache |= _CPTableViewHeightOfRowSelector; - _hasVariableHeightRows = YES; + tableColumnObjectValues = []; + _objectValues[tableColumnUID] = tableColumnObjectValues; } - else - _hasVariableHeightRows = NO; -} + var objectValue = tableColumnObjectValues[aRowIndex]; -/* - Tells the table view that the number of rows in the table - has changed. -*/ -- (void)noteNumberOfRowsChanged -{ - var numberOfRows = [_dataSource numberOfRowsInTableView:self]; - - if (_numberOfRows != numberOfRows) + if (objectValue === undefined) { - _numberOfRows = numberOfRows; - - [self _recalculateColumnHeight]; + objectValue = [_dataSource tableView:self objectValueForTableColumn:aTableColumn row:aRowIndex]; + tableColumnObjectValues[aRowIndex] = objectValue; } + + return objectValue; } -- (void)noteHeightOfRowsWithIndexesChanged:(CPIndexSet)indexSet -{ - // FIXME: more efficient version is possible since we know which indexes changes - [self _recalculateColumnHeight]; -} - -/* - Returns the rectangle bounding the specified row. - @param aRowIndex the row to obtain a rectangle for - @return the bounding rectangle -*/ -- (CGRect)rectOfRow:(int)aRowIndex -{ - return CPRectMake(0.0, ROW_MIN_Y(aRowIndex), CPRectGetWidth([self bounds]), ROW_HEIGHT(aRowIndex)); -} - -/* - Returns the rectangle bounding the specified column - @param aColumnIndex the column to obtain a rectangle for - @return the bounding column -*/ -- (CGRect)rectOfColumn:(int)aColumnIndex -{ - return [_tableColumnViews[aColumnIndex] frame]; -} - -/* - Adjusts column widths to make them all visible at once. Same as \c -tile. -*/ -- (void)sizeToFit -{ -// [self tile]; -} - -- (void)_recalculateColumnHeight -{ - var oldColumnHeight = _columnHeight; - - if (_hasVariableHeightRows) - { - _rowMinYs[0] = 0; - for (var row = 0; row < _numberOfRows; row++) - { - _rowHeights[row] = [_delegate tableView:self heightOfRow:row]; - _rowMinYs[row+1] = _rowMinYs[row] + _rowHeights[row] + _intercellSpacing.height; - } - _columnHeight = _rowMinYs[_numberOfRows]; // last index is one more than last row, and is the total column height - } - else - _columnHeight = _numberOfRows * (_rowHeight + _intercellSpacing.height); - - var count = _tableColumnViews.length; - - while (count--) - [_tableColumnViews[count] setFrameSize:CGSizeMake([_tableColumns[count] width], _columnHeight)]; - - [self setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), _columnHeight)]; -} - -- (CGRect)visibleRectInParent +- (CGRect)_exposedRect { var superview = [self superview]; - - if (!superview) + + if (![superview isKindOfClass:[CPClipView class]]) return [self bounds]; - + return [self convertRect:CGRectIntersection([superview bounds], [self frame]) fromView:superview]; } -/* - Reloads the data from the \c -dataSource. This is an - expensive method, so use it lightly. -*/ -- (void)reloadData +- (void)load { - var oldNumberOfRows = _numberOfRows; - - _numberOfRows = [_dataSource numberOfRowsInTableView:self]; - - if (oldNumberOfRows != _numberOfRows) + if (!_dataSource) { - [self _recalculateColumnHeight]; - [self setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), [self _columnHeight])]; + // remove? + return; } - - _objectValueCache = []; - - [self clearCells]; - - [self setNeedsLayout]; + +// if (!window.blah) +// return window.setTimeout(function() { window.blah = true; [self load]; window.blah = false}, 0.0); + + // if (window.console && window.console.profile) + // console.profile("cell-load"); + + var exposedRect = [self _exposedRect], + exposedRows = [CPIndexSet indexSetWithIndexesInRange:[self rowsInRect:exposedRect]], + exposedColumns = [self columnIndexesInRect:exposedRect], + obscuredRows = [_exposedRows copy], + obscuredColumns = [_exposedColumns copy]; + + [obscuredRows removeIndexes:exposedRows]; + [obscuredColumns removeIndexes:exposedColumns]; + + var newlyExposedRows = [exposedRows copy], + newlyExposedColumns = [exposedColumns copy]; + + [newlyExposedRows removeIndexes:_exposedRows]; + [newlyExposedColumns removeIndexes:_exposedColumns]; + + var previouslyExposedRows = [exposedRows copy], + previouslyExposedColumns = [exposedColumns copy]; + + [previouslyExposedRows removeIndexes:newlyExposedRows]; + [previouslyExposedColumns removeIndexes:newlyExposedColumns]; + +// console.log("will remove:" + '\n\n' + +// previouslyExposedRows + "\n" + obscuredColumns + "\n\n" + +// obscuredRows + "\n" + previouslyExposedColumns + "\n\n" + +// obscuredRows + "\n" + obscuredColumns); + [self _unloadDataViewsInRows:previouslyExposedRows columns:obscuredColumns]; + [self _unloadDataViewsInRows:obscuredRows columns:previouslyExposedColumns]; + [self _unloadDataViewsInRows:obscuredRows columns:obscuredColumns]; + + [self _loadDataViewsInRows:previouslyExposedRows columns:newlyExposedColumns]; + [self _loadDataViewsInRows:newlyExposedRows columns:previouslyExposedColumns]; + [self _loadDataViewsInRows:newlyExposedRows columns:newlyExposedColumns]; + +// console.log("newly exposed rows: " + newlyExposedRows + "\nnewly exposed columns: " + newlyExposedColumns); + _exposedRows = exposedRows; + _exposedColumns = exposedColumns; + + [_tableDrawView setFrame:exposedRect]; + +// [_tableDrawView setBounds:exposedRect]; + [_tableDrawView display]; + + // if (window.console && window.console.profile) +// console.profileEnd("cell-load"); +} + +- (void)_unloadDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns +{ + if (![rows count] || ![columns count]) + return; + + var rowArray = [], + columnArray = []; + + [rows getIndexes:rowArray maxCount:-1 inIndexRange:nil]; + [columns getIndexes:columnArray maxCount:-1 inIndexRange:nil]; + + var columnIndex = 0, + columnsCount = columnArray.length; + + for (; columnIndex < columnsCount; ++columnIndex) + { + var column = columnArray[columnIndex], + tableColumn = _tableColumns[column], + tableColumnUID = [tableColumn UID]; + + var rowIndex = 0, + rowsCount = rowArray.length; + + for (; rowIndex < rowsCount; ++rowIndex) + { + var row = rowArray[rowIndex], + dataView = _dataViewsForTableColumns[tableColumnUID][row]; + + _dataViewsForTableColumns[tableColumnUID][row] = nil; +if (!_cachedDataViews[dataView.identifier]) +_cachedDataViews[dataView.identifier] = [dataView]; +else +_cachedDataViews[dataView.identifier].push(dataView); + } + } +} + +- (void)_loadDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns +{ + if (![rows count] || ![columns count]) + return; + + var rowArray = [], + rowRects = [], + columnArray = []; + + [rows getIndexes:rowArray maxCount:-1 inIndexRange:nil]; + [columns getIndexes:columnArray maxCount:-1 inIndexRange:nil]; + + var columnIndex = 0, + columnsCount = columnArray.length; + + for (; columnIndex < columnsCount; ++columnIndex) + { + var column = columnArray[columnIndex], + tableColumn = _tableColumns[column], + tableColumnUID = [tableColumn UID], + tableColumnRange = _tableColumnRanges[column]; + + if (!_dataViewsForTableColumns[tableColumnUID]) + _dataViewsForTableColumns[tableColumnUID] = []; + + var rowIndex = 0, + rowsCount = rowArray.length; + + for (; rowIndex < rowsCount; ++rowIndex) + { + var row = rowArray[rowIndex], + dataView = [tableColumn _newDataViewForRow:row], + rectOfRow = rowRects[row]; + + if (!rectOfRow) + rectOfRow = rowRects[row] = [self rectOfRow:row]; + + [dataView setFrame:_CGRectMake(tableColumnRange.location, _CGRectGetMinY(rectOfRow), tableColumnRange.length, _CGRectGetHeight(rectOfRow))]; + [dataView setObjectValue:[self _objectValueForTableColumn:tableColumn row:row]]; + + if ([dataView superview] !== self) + [self addSubview:dataView]; + + _dataViewsForTableColumns[tableColumnUID][row] = dataView; + } + } +} + +- (void)setFrameSize:(CGSize)aSize +{ + [super setFrameSize:aSize]; + [_headerView setFrameSize:CGSizeMake(aSize.width, [_headerView frame].size.height)]; +} + +- (CGRect)exposedClipRect +{ + var superview = [self superview]; + + if (![superview isKindOfClass:[CPClipView class]]) + return [self bounds]; + + return [self convertRect:CGRectIntersection([superview bounds], [self frame]) fromView:superview]; +} + +- (void)_drawRect:(CGRect)aRect +{ + var exposedRect = [self _exposedRect]; + + [self drawBackgroundInClipRect:exposedRect]; + [self highlightSelectionInClipRect:exposedRect]; + [self drawGridInClipRect:exposedRect]; +} + +- (void)drawBackgroundInClipRect:(CGRect)aRect +{ + if (![self usesAlternatingRowBackgroundColors]) + return; + + var rowColors = [self alternatingRowBackgroundColors], + colorCount = [rowColors count]; + + if (colorCount === 0) + return; + + var context = [[CPGraphicsContext currentContext] graphicsPort]; + + if (colorCount === 1) + { + CGContextSetFillColor(context, rowColors[0]); + CGContextFillRect(context, aRect); + + return; + } + // CGContextFillRect(context, CGRectIntersection(aRect, fillRect)); + // console.profile("row-paint"); + var exposedRows = [self rowsInRect:aRect], + firstRow = exposedRows.location, + lastRow = CPMaxRange(exposedRows) - 1, + colorIndex = MIN(exposedRows.length, colorCount); + + while (colorIndex--) + { + var row = firstRow % colorCount + firstRow + colorIndex, + fillRect = nil; + + CGContextBeginPath(context); + + for (; row <= lastRow; row += colorCount) + CGContextAddRect(context, CGRectIntersection(aRect, fillRect = [self rectOfRow:row])); + + if (row - colorCount === lastRow) + heightFilled = _CGRectGetMaxY(fillRect); + + CGContextClosePath(context); + + CGContextSetFillColor(context, rowColors[colorIndex]); + CGContextFillPath(context); + } + // console.profileEnd("row-paint"); + + var totalHeight = _CGRectGetMaxY(aRect); + + if (heightFilled >= totalHeight || _rowHeight <= 0.0) + return; + + var rowHeight = _rowHeight + _intercellSpacing.height, + fillRect = _CGRectMake(_CGRectGetMinX(aRect), _CGRectGetMinY(aRect) + heightFilled, _CGRectGetWidth(aRect), rowHeight); + + for (row = lastRow + 1; heightFilled < totalHeight; ++row) + { + CGContextSetFillColor(context, rowColors[row % colorCount]); + CGContextFillRect(context, fillRect); + + heightFilled += rowHeight; + fillRect.origin.y += rowHeight; + } +} + +- (void)drawGridInClipRect:(CGRect)aRect +{ + var context = [[CPGraphicsContext currentContext] graphicsPort], + gridStyleMask = [self gridStyleMask]; + + if (!(gridStyleMask & (CPTableViewSolidHorizontalGridLineMask | CPTableViewSolidVerticalGridLineMask))) + return; + + CGContextBeginPath(context); + + if (gridStyleMask & CPTableViewSolidHorizontalGridLineMask) + { + var exposedRows = [self rowsInRect:aRect]; + row = exposedRows.location, + lastRow = CPMaxRange(exposedRows) - 1, + rowY = 0.0, + minX = _CGRectGetMinX(aRect), + maxX = _CGRectGetMaxX(aRect); + + for (; row <= lastRow; ++row) + { + // grab each row rect and add the top and bottom lines + var rowRect = [self rectOfRow:row], + rowY = _CGRectGetMaxY(rowRect) - 0.5; + + CGContextMoveToPoint(context, minX, rowY); + CGContextAddLineToPoint(context, maxX, rowY); + } + + if (_rowHeight > 0.0) + { + var rowHeight = _rowHeight + _intercellSpacing.height, + totalHeight = _CGRectGetMaxY(aRect); + + while (rowY < totalHeight) + { + rowY += rowHeight; + + CGContextMoveToPoint(context, minX, rowY); + CGContextAddLineToPoint(context, maxX, rowY); + } + } + } + + if (gridStyleMask & CPTableViewSolidVerticalGridLineMask) + { + var exposedColumnIndexes = [self columnIndexesInRect:aRect], + columnsArray = []; + + [exposedColumnIndexes getIndexes:columnsArray maxCount:-1 inIndexRange:nil]; + + var columnArrayIndex = 0, + columnArrayCount = columnsArray.length, + minY = _CGRectGetMinY(aRect), + maxY = _CGRectGetMaxY(aRect); + + for (; columnArrayIndex < columnArrayCount; ++columnArrayIndex) + { + var columnRect = [self rectOfColumn:columnArrayIndex], + columnX = _CGRectGetMaxX(columnRect) - 0.5; + + CGContextMoveToPoint(context, columnX, minY); + CGContextAddLineToPoint(context, columnX, maxY); + } + } + + CGContextClosePath(context); + CGContextSetStrokeColor(context, _gridColor); + CGContextStrokePath(context); +} + + +- (void)highlightSelectionInClipRect:(CGRect)aRect +{ + // FIXME: This color thingy is terrible probably. + if ([self selectionHighlightStyle] === CPTableViewSelectionHighlightStyleSourceList) + [[CPColor selectionColorSourceView] setFill]; + else + [[CPColor selectionColor] setFill]; + + var context = [[CPGraphicsContext currentContext] graphicsPort], + indexes = [], + rectSelector = @selector(rectOfRow:); + + if ([_selectedRowIndexes count] >= 1) + { + var exposedRows = [CPIndexSet indexSetWithIndexesInRange:[self rowsInRect:aRect]], + firstRow = [exposedRows firstIndex], + exposedRange = CPMakeRange(firstRow, [exposedRows lastIndex] - firstRow + 1); + + [_selectedRowIndexes getIndexes:indexes maxCount:-1 inIndexRange:exposedRange]; + } + + else if ([_selectedColumnIndexes count] >= 1) + { + rectSelector = @selector(rectOfColumn:); + + var exposedColumns = [self columnIndexesInRect:aRect], + firstColumn = [exposedColumns firstIndex], + exposedRange = CPMakeRange(firstColumn, [exposedColumns lastIndex] - firstColumn + 1); + + [_selectedColumnIndexes getIndexes:indexes maxCount:-1 inIndexRange:exposedRange]; + } + + var count = [indexes count]; + + if (!count) + return; + + CGContextBeginPath(context); + + while (count--) + CGContextAddRect(context, CGRectIntersection(objj_msgSend(self, rectSelector, indexes[count]), aRect)); + + CGContextClosePath(context); + CGContextFillPath(context); } - (void)layoutSubviews { - [self loadTableCellsInRect:[self visibleRectInParent]]; + [self load]; } -- (void)displaySoon +- (void)viewWillMoveToSuperview:(CPView)aView { - [_scrollTimer invalidate]; - _scrollTimer = [CPTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(displayNow) userInfo:nil repeats:NO]; + var superview = [self superview], + defaultCenter = [CPNotificationCenter defaultCenter]; + + if (superview) + { + [defaultCenter + removeObserver:self + name:CPViewFrameDidChangeNotification + object:superview]; + + [defaultCenter + removeObserver:self + name:CPViewBoundsDidChangeNotification + object:superview]; + } + + if (aView) + { + [aView setPostsFrameChangedNotifications:YES]; + [aView setPostsBoundsChangedNotifications:YES]; + + [defaultCenter + addObserver:self + selector:@selector(superviewFrameChanged:) + name:CPViewFrameDidChangeNotification + object:aView]; + + [defaultCenter + addObserver:self + selector:@selector(superviewBoundsChanged:) + name:CPViewBoundsDidChangeNotification + object:aView]; + } } -- (void)displayNow +- (void)superviewBoundsChanged:(CPNotification)aNotification { + [self setNeedsDisplay:YES]; [self setNeedsLayout]; } -- (void)viewDidMoveToSuperview +- (void)superviewFrameChanged:(CPNotification)aNotification { - [[[self enclosingScrollView] contentView] setPostsBoundsChangedNotifications:YES]; - - [[CPNotificationCenter defaultCenter] - addObserver:self - selector:@selector(viewBoundsChanged:) - name:CPViewBoundsDidChangeNotification - object:[[self enclosingScrollView] contentView]]; + [self _sizeToParent]; + + // Call this explicitly because *our* size may not change, but the exposedRect may change. + [self setNeedsDisplay:YES]; + [self setNeedsLayout]; } -- (void)viewBoundsChanged:(CPNotification)aNotification -{ - //CPLog.info(_cmd + CPStringFromRect([[[self enclosingScrollView] contentView] bounds])); - //objj_debug_print_backtrace(); - //[self setNeedsLayout]; - [self displayNow]; -} +// /* -- (void)setAllowsColumnReordering:(BOOL)allowsColumnReordering -{ - if (_allowsColumnReordering === _allowsColumnReordering) - return; - - _allowsColumnReordering = allowsColumnReordering; -} -- (void)allowsColumnReordering -{ - return _allowsColumnReordering; -} - -- (void)setAllowsColumnResizing:(BOOL)allowsColumnResizing -{ - if (_allowsColumnResizing === allowsColumnResizing) - return; - - _allowsColumnResizing = allowsColumnResizing; -} -- (void)allowsColumnResizing -{ - return _allowsColumnResizing; -} - -- (void)setAllowsColumnSelection:(BOOL)allowsColumnSelection -{ - if (_allowsColumnSelection === allowsColumnSelection) - return; - - _allowsColumnSelection = allowsColumnSelection; -} -- (void)allowsColumnSelection -{ - return _allowsColumnSelection; -} -*/ - -- (void)setAllowsMultipleSelection:(BOOL)allowsMultipleSelection -{ - if (_allowsMultipleSelection === allowsMultipleSelection) - return; - - _allowsMultipleSelection = allowsMultipleSelection; - - // TODO: more stuff? -} -- (void)allowsMultipleSelection -{ - return _allowsMultipleSelection; -} - -- (void)setAllowsEmptySelection:(BOOL)allowsEmptySelection -{ - if (_allowsEmptySelection === allowsEmptySelection) - return; - - _allowsEmptySelection = allowsEmptySelection; -} -- (void)allowsEmptySelection -{ - return _allowsEmptySelection; -} - - -/* - Returns the index of the row at the given point, or CPNotFound (-1) if it is out of range. - @param aPoint the point - @return the index of the row at aPoint -*/ -- (int)rowAtPoint:(CGPoint)aPoint -{ - var index = [self _rowAtY:aPoint.y] - - if (index >= 0 && index < _numberOfRows) - return index; - else - return CPNotFound; -} - -- (int)columnAtPoint:(CGPoint)aPoint -{ - var index = [self _columnAtX:aPoint.x] - - if (index >= 0 && index < _tableColumns.length) - return index; - else - return CPNotFound; -} - -/* - @ignore - - Internal version takes a Y value, returns an index, or -1 if its beyond the min, or numberOfRows if it's beyond the max -*/ -- (int)_rowAtY:(float)y -{ - if (_hasVariableHeightRows) - { - var a = 0, - b = _numberOfRows; - - if (y < _rowMinYs[0]) - return -1; - if (y >= _rowMinYs[_rowMinYs.length-1]) - return _numberOfRows; - - // binary search - while (true) - { - var half = a + Math.floor((b - a) / 2); - - if (y < _rowMinYs[half]) - b = half; - else if (half < _numberOfRows-1 && y >= _rowMinYs[half+1]) - a = half; - else - return half; - } - } - else - return FLOOR(y / (_rowHeight + _intercellSpacing.height)); -} - -/* - @ignore - - Internal version takes a X value, returns an index, or -1 if its beyond the min, or numberOfColumns if it's beyond the max -*/ -- (int)_columnAtX:(float)x -{ - var a = 0, - b = _tableColumns.length; - - var last = [_tableColumnViews[_tableColumns.length-1] frame]; - if (x < [_tableColumnViews[0] frame].origin.x) - return -1; - if (x >= last.origin.x + last.size.width) - return _tableColumns.length; - - // binary search - while (true) - { - var half = a + Math.floor((b - a) / 2); - - if (x < [_tableColumnViews[half] frame].origin.x) - b = half; - else if (half < _tableColumns.length-1 && x >= [_tableColumnViews[half+1] frame].origin.x) - a = half; - else - return half; - } -} - -/* - Selects the specified row indexes, optionally adding to existing selection - @param indexes the indexes to select - @param extend whether or not to add to the existing selection -*/ -- (void)selectRowIndexes:(CPIndexSet)indexes byExtendingSelection:(BOOL)extend -{ - // FIXME: should this be subject to the delegate filters, etc? - - if (extend) - _selectedRowIndexes = [[_selectedRowIndexes copy] addIndexes:indexes]; - else if ([indexes count] > 0 || _allowsEmptySelection) - _selectedRowIndexes = [indexes copy]; - - [self _drawSelection]; -} - -/* - Returns a CPIndexSet of the selected rows - @return indexes of the selected rows -*/ -- (CPIndexSet)selectedRowIndexes -{ - return _selectedRowIndexes; -} - -/* - Returns the number of selected rows - @return number of selected rows -*/ -- (int)numberOfSelectedRows -{ - return [_selectedRowIndexes count]; -} - - -/* - Deselects all rows if allowsEmptySelection is true. If delegate responds to "selectionShouldChangeInTableView:", asks if it should chnage. - Sends the CPTableViewSelectionDidChangeNotification on deselection. - @param the sender in a target/action -*/ -- (void)deselectAll:(id)sender -{ - if (!_allowsEmptySelection || [_selectedRowIndexes count] === 0 || - ((_delegateSelectorsCache & _CPTableViewSelectionShouldChangeSelector) && ![_delegate selectionShouldChangeInTableView:self])) - return; - - [self selectRowIndexes:[CPIndexSet indexSet] byExtendingSelection:NO]; - [[CPNotificationCenter defaultCenter] postNotificationName:CPTableViewSelectionDidChangeNotification object:self userInfo:nil]; -} - -- (void)editColumn:(int)columnIndex row:(int)rowIndex withEvent:(CPEvent)theEvent select:(BOOL)flag -{ - -} - -/* - @ignore -*/ -- (void)_updateSelectionWithMouseAtRow:(int)aRow -{ - // Make a preliminary new selection - var newSelection; - if (_allowsMultipleSelection) - newSelection = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(MIN(aRow, _selectionStartRow), ABS(aRow-_selectionStartRow)+1)]; - else if (aRow >= 0 && aRow < _numberOfRows) - newSelection = [CPIndexSet indexSetWithIndex:aRow]; - else - newSelection = [CPIndexSet indexSet]; - - // If cmd/ctrl was held down XOR the old selection with the proposed selection - if (_allowsMultipleSelection && _selectionModifier & (CPCommandKeyMask | CPControlKeyMask | CPAlternateKeyMask)) - { - // A = newSelection, B = _previousSelectedRowIndexes - // (A intersection B) = (A - (A - B)) - var intersection = [newSelection copy], - difference = [newSelection copy]; - [difference removeIndexes:_previousSelectedRowIndexes]; - [intersection removeIndexes:difference] - - // (A xor B) = (A + B) - (A intersection B) - [newSelection addIndexes:_previousSelectedRowIndexes]; - [newSelection removeIndexes:intersection]; - - // FIXME: if multiple selection is off, and we cmd/ctrl click the previously selected row, then deselect it. - } - - // if the new selection is different than the old selection - if (![newSelection isEqualToIndexSet:_selectedRowIndexes]) - { - // ask the delegate if we should change the selection - if ((_delegateSelectorsCache & _CPTableViewSelectionShouldChangeSelector) && ![_delegate selectionShouldChangeInTableView:self]) - return; - - // ask the delegate which indexes can be selected. selectionIndexesForProposedSelection is faster than shouldSelectRow - if (_delegateSelectorsCache & _CPTableViewSelectionIndexesForProposedSelectionSelector) - newSelection = [_delegate tableView:self selectionIndexesForProposedSelection:newSelection]; - else if (_delegateSelectorsCache & _CPTableViewShouldSelectRowSelector) - { - var indexes = []; - [newSelection getIndexes:indexes maxCount:Number.MAX_VALUE inIndexRange:nil]; - for (var i = 0; i < indexes.length; i++) - if (![_delegate tableView:self shouldSelectRow:indexes[i]]) - [newSelection removeIndex:indexes[i]]; - } - } - - // if empty selection is not allowed and the new selection has nothing selected, abort - if (!_allowsEmptySelection && [newSelection count] === 0) - return; - - // if the new selection is *still* different, and update the selection and send a notification - if (![newSelection isEqualToIndexSet:_selectedRowIndexes]) - { - [self selectRowIndexes:newSelection byExtendingSelection:NO]; - [[CPNotificationCenter defaultCenter] postNotificationName:CPTableViewSelectionIsChangingNotification object:self userInfo:nil]; - } -} - -/* - @ignore -*/ -- (void)mouseDown:(CPEvent)anEvent -{ - [self trackSelection:anEvent]; -} - -/* - Sets the message to be sent to the target when a cell is double clicked - @param aSelector the selector to be performed -*/ -- (void)setDoubleAction:(SEL)aSelector -{ - _doubleAction = aSelector; -} -- (SEL)doubleAction -{ - return _doubleAction; -} - -- (int)clickedColumn -{ - return _clickedColumn; -} -- (int)clickedRow -{ - return _clickedRow; -} - -/* - @ignore -*/ -- (void)trackSelection:(CPEvent)anEvent -{ var type = [anEvent type], point = [self convertPoint:[anEvent locationInWindow] fromView:nil], currentRow = MAX(0, MIN(_numberOfRows-1, [self _rowAtY:point.y])); - - if (type == CPLeftMouseUp) - { - _clickedRow = [self rowAtPoint:point]; - _clickedColumn = [self columnAtPoint:point]; - - if ([anEvent clickCount] === 2) - { - CPLog.warn("edit?!"); - - [self sendAction:_doubleAction to:_target]; - } - else - { - if (![_previousSelectedRowIndexes isEqualToIndexSet:_selectedRowIndexes]) - { - [[CPNotificationCenter defaultCenter] postNotificationName:CPTableViewSelectionDidChangeNotification object:self userInfo:nil]; - } - - [self sendAction:_action to:_target]; - } - - return; - } - - if (type == CPLeftMouseDown) - { - _previousSelectedRowIndexes = _selectedRowIndexes; - _selectionModifier = [anEvent modifierFlags]; - - if (_selectionModifier & CPShiftKeyMask) - _selectionStartRow = (ABS([_previousSelectedRowIndexes firstIndex] - currentRow) < ABS([_previousSelectedRowIndexes lastIndex] - currentRow)) ? - [_previousSelectedRowIndexes firstIndex] : [_previousSelectedRowIndexes lastIndex]; - else - _selectionStartRow = currentRow; - - [self _updateSelectionWithMouseAtRow:currentRow]; - } - else if (type == CPLeftMouseDragged) - { - [self _updateSelectionWithMouseAtRow:currentRow]; - } - - [CPApp setTarget:self selector:@selector(trackSelection:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES]; + +*/ + +- (BOOL)tracksMouseOutsideOfFrame +{ + return YES; } -/* - @ignore -*/ -- (void)_drawSelection +- (BOOL)startTrackingAt:(CGPoint)aPoint { - if (!_currentlySelected) { - _currentlySelected = [CPIndexSet indexSet]; - _selectionViews = []; - _selectionViewsPool = []; - } + var row = [self rowAtPoint:aPoint]; - // TODO: we could also remove selections that aren't visible, but then we'll need to run this on every scroll/resize? - - // get array of indexes we can remove - var removeSet = [_currentlySelected copy], - indexesToRemove = []; - [removeSet removeIndexes:_selectedRowIndexes]; - [removeSet getIndexes:indexesToRemove maxCount:Number.MAX_VALUE inIndexRange:nil]; - - // get array of indexes we need to add - var addSet = [_selectedRowIndexes copy], - indexesToAdd = []; - [addSet removeIndexes:_currentlySelected]; - [addSet getIndexes:indexesToAdd maxCount:Number.MAX_VALUE inIndexRange:nil]; - - for (var i = 0; i < indexesToRemove.length; i++) - { - var row = indexesToRemove[i]; - for (var column = 0; column < _tableColumns.length; column++) - if ([_tableCells[column][row] respondsToSelector:@selector(highlight:)]) - [_tableCells[column][row] highlight:NO]; - } - for (var i = 0; i < indexesToAdd.length; i++) - { - var row = indexesToAdd[i]; - for (var column = 0; column < _tableColumns.length; column++) - if ([_tableCells[column][row] respondsToSelector:@selector(highlight:)]) - [_tableCells[column][row] highlight:YES]; - } + if ([self mouseDownFlags] & CPShiftKeyMask) + _selectionAnchorRow = (ABS([_selectedRowIndexes firstIndex] - row) < ABS([_selectedRowIndexes lastIndex] - row)) ? + [_selectedRowIndexes firstIndex] : [_selectedRowIndexes lastIndex]; + else + _selectionAnchorRow = row; - // add each one we need to add, taking the selection views from removed seelctions, the pool, or new - for (var i = 0; i < indexesToAdd.length; i++) + [self _updateSelectionWithMouseAtRow:row]; + + return YES; +} + +- (BOOL)continueTracking:(CGPoint)lastPoint at:(CGPoint)aPoint +{ + [self _updateSelectionWithMouseAtRow:[self rowAtPoint:aPoint]]; + + return YES; +} + +- (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp +{/* + _clickedRow = [self rowAtPoint:point]; + _clickedColumn = [self columnAtPoint:point]; + + if ([anEvent clickCount] === 2) { - var index = indexesToAdd[i], - view; - - if (indexesToRemove.length > 0) + CPLog.warn("edit?!"); + + [self sendAction:_doubleAction to:_target]; + } + else + { + if (![_previousSelectedRowIndexes isEqualToIndexSet:_selectedRowIndexes]) { - view = _selectionViews[indexesToRemove.pop()]; + [[CPNotificationCenter defaultCenter] postNotificationName:CPTableViewSelectionDidChangeNotification object:self userInfo:nil]; } - else if (_selectionViewsPool.length > 0) + + [self sendAction:_action to:_target]; + } +*/ +} + +- (void)_updateSelectionWithMouseAtRow:(CPInteger)aRow +{ + // If cmd/ctrl was held down XOR the old selection with the proposed selection + if ([self mouseDownFlags] & (CPCommandKeyMask | CPControlKeyMask | CPAlternateKeyMask)) + { + if ([_selectedRowIndexes containsIndex:aRow]) { - view = _selectionViewsPool.pop(); - [self addSubview:view positioned:CPWindowBelow relativeTo:nil]; + newSelection = [_selectedRowIndexes copy]; + + [newSelection removeIndex:aRow]; } + + else if (_allowsMultipleSelection) + { + newSelection = [_selectedRowIndexes copy]; + + [newSelection addIndex:aRow]; + } + else - { - view = [[CPView alloc] init]; - [view setBackgroundColor:[CPColor alternateSelectedControlColor]]; - - [self addSubview:view positioned:CPWindowBelow relativeTo:nil]; - } - - _selectionViews[index] = view; - - var frame = [self rectOfRow:index]; - frame.size.height += _intercellSpacing.height - 1; - //frame.size.width += 500; - - [view setFrame:frame]; + newSelection = [CPIndexSet indexSetWithIndex:aRow]; } - - // remove any selections that weren't already reused - for (var i = 0; i < indexesToRemove.length; i++) + + else if (_allowsMultipleSelection) + newSelection = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(MIN(aRow, _selectionAnchorRow), ABS(aRow - _selectionAnchorRow) + 1)]; + + else if (aRow >= 0 && aRow < _numberOfRows) + newSelection = [CPIndexSet indexSetWithIndex:aRow]; + + else + newSelection = [CPIndexSet indexSet]; + + if ([newSelection isEqualToIndexSet:_selectedRowIndexes]) + return; + + if (_implementedDelegateMethods & CPTableViewDelegate_selectionShouldChangeInTableView_ && + ![_delegate selectionShouldChangeInTableView:self]) + return; + + if (_implementedDelegateMethods & CPTableViewDelegate_tableView_selectionIndexesForProposedSelection_) + newSelection = [_delegate tableView:self selectionIndexesForProposedSelection:newSelection]; + + if (_implementedDelegateMethods & CPTableViewDelegate_tableView_shouldSelectRow_) { - var row = indexesToRemove[i], - view = _selectionViews[row]; - - [view removeFromSuperview]; - _selectionViewsPool.push(view); + var indexArray = []; + + [newSelection getIndexes:indexArray maxCount:-1 inIndexRange:nil]; + + var indexCount = indexArray.length; + + while (indexCount--) + { + var index = indexArray[indexCount]; + + if (![_delegate tableView:self shouldSelectRow:index]) + [newSelection removeIndex:index]; + } } - - // update the currently selected index set - _currentlySelected = [_selectedRowIndexes copy]; + + // if empty selection is not allowed and the new selection has nothing selected, abort + if (!_allowsEmptySelection && [newSelection count] === 0) + return; + + if ([newSelection isEqualToIndexSet:_selectedRowIndexes]) + return; + + [self selectRowIndexes:newSelection byExtendingSelection:NO]; + + [[CPNotificationCenter defaultCenter] + postNotificationName:CPTableViewSelectionIsChangingNotification + object:self + userInfo:nil]; } @end - var CPTableViewDataSourceKey = @"CPTableViewDataSourceKey", CPTableViewDelegateKey = @"CPTableViewDelegateKey", CPTableViewHeaderViewKey = @"CPTableViewHeaderViewKey", @@ -1246,10 +1684,13 @@ var CPTableViewDataSourceKey = @"CPTableViewDataSourceKey", - (id)initWithCoder:(CPCoder)aCoder { - if (self = [super initWithCoder:aCoder]) + //FIXME:!!!! + [self init]; + + self = [super initWithCoder:aCoder]; + + if (self) { - [self _init]; - _dataSource = [aCoder decodeObjectForKey:CPTableViewDataSourceKey]; _delegate = [aCoder decodeObjectForKey:CPTableViewDelegateKey]; @@ -1259,9 +1700,12 @@ var CPTableViewDataSourceKey = @"CPTableViewDataSourceKey", _allowsMultipleSelection = [aCoder decodeBoolForKey:CPTableViewMultipleSelectionKey]; _allowsEmptySelection = [aCoder decodeBoolForKey:CPTableViewEmptySelectionKey]; - var tableColumns = [aCoder decodeObjectForKey:CPTableViewTableColumnsKey]; - for (var i = 0; i < tableColumns.length; i++) - [self addTableColumn:tableColumns[i]]; + _tableColumns = [aCoder decodeObjectForKey:CPTableViewTableColumnsKey]; + + [_tableColumns makeObjectsPerformSelector:@selector(setTableView:) withObject:self]; + _dirtyTableColumnRangeIndex = 0; + + [self viewWillMoveToSuperview:[self superview]]; } return self; @@ -1274,29 +1718,28 @@ var CPTableViewDataSourceKey = @"CPTableViewDataSourceKey", [aCoder encodeObject:_dataSource forKey:CPTableViewDataSourceKey]; [aCoder encodeObject:_delegate forKey:CPTableViewDelegateKey]; - [aCoder encodeObject:_tableColumns forKey:CPTableViewTableColumnsKey]; - [aCoder encodeFloat:_rowHeight forKey:CPTableViewRowHeightKey]; [aCoder encodeSize:_intercellSpacing forKey:CPTableViewIntercellSpacingKey]; [aCoder encodeBool:_allowsMultipleSelection forKey:CPTableViewMultipleSelectionKey]; [aCoder encodeBool:_allowsEmptySelection forKey:CPTableViewEmptySelectionKey]; + + [aCoder encodeObject:_tableColumns forKey:CPTableViewTableColumnsKey]; } @end +@implementation CPColor (tableview) - -@implementation CPColor (TableView) - -+ (CPColor)alternateSelectedControlColor ++ (CPColor)selectionColor { - return [[CPColor alloc] _initWithRGBA:[0.22, 0.46, 0.84, 1.0]]; + return [CPColor colorWithHexString:@"5f83b9"]; } -+ (CPColor)secondarySelectedControlColor ++ (CPColor)selectionColorSourceView { - return [[CPColor alloc] _initWithRGBA:[0.83, 0.83, 0.83, 1.0]]; + return [CPColor colorWithPatternImage:[[CPImage alloc] initByReferencingFile:@"Resources/tableviewselection.png" size:CGSizeMake(6,22)]]; } -@end + +@end \ No newline at end of file diff --git a/AppKit/CPTextField.j b/AppKit/CPTextField.j index 87ea6f8c9..6672a4596 100644 --- a/AppKit/CPTextField.j +++ b/AppKit/CPTextField.j @@ -692,7 +692,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); { var string = [self stringValue]; - if ((!string || [string length] === 0) && ![self hasThemeState:CPThemeStateEditing]) + if ((!string || string.length === 0) && ![self hasThemeState:CPThemeStateEditing]) [self setThemeState:CPTextFieldStatePlaceholder]; else [self unsetThemeState:CPTextFieldStatePlaceholder]; diff --git a/AppKit/CPView.j b/AppKit/CPView.j index 047e2ccc4..504569d5b 100644 --- a/AppKit/CPView.j +++ b/AppKit/CPView.j @@ -94,12 +94,13 @@ var DOMElementPrototype = nil, BackgroundTrivialColor = 0, BackgroundVerticalThreePartImage = 1, BackgroundHorizontalThreePartImage = 2, - BackgroundNinePartImage = 3, - - CustomDrawRectViews = {}, - CustomLayoutSubviewsViews = {}; + BackgroundNinePartImage = 3; #endif +var CPViewFlags = { }, + CPViewHasCustomDrawRect = 1 << 0, + CPViewHasCustomLayoutSubviews = 1 << 1; + /*! @ingroup appkit @class CPView @@ -184,6 +185,8 @@ var DOMElementPrototype = nil, // Key View Support CPView _nextKeyView; CPView _previousKeyView; + + unsigned _viewClassFlags; } /* @@ -209,6 +212,27 @@ var DOMElementPrototype = nil, CachedNotificationCenter = [CPNotificationCenter defaultCenter]; } +- (void)setupViewFlags +{ + var theClass = [self class], + classUID = [theClass UID]; + + if (CPViewFlags[classUID] === undefined) + { + var flags = 0; + + if ([theClass instanceMethodForSelector:@selector(drawRect:)] !== [CPView instanceMethodForSelector:@selector(drawRect:)]) + flags |= CPViewHasCustomDrawRect; + + if ([theClass instanceMethodForSelector:@selector(layoutSubviews)] !== [CPView instanceMethodForSelector:@selector(layoutSubviews)]) + flags |= CPViewHasCustomLayoutSubviews; + + CPViewFlags[classUID] = flags; + } + + _viewClassFlags = CPViewFlags[classUID]; +} + + (CPSet)keyPathsForValuesAffectingFrame { return [CPSet setWithObjects:@"frameOrigin", @"frameSize"]; @@ -261,6 +285,8 @@ var DOMElementPrototype = nil, _theme = [CPTheme defaultTheme]; _themeState = CPThemeStateNormal; + [self setupViewFlags]; + [self _loadThemeAttributes]; } @@ -1549,16 +1575,7 @@ setBoundsOrigin: - (void)setNeedsDisplayInRect:(CPRect)aRect { #if PLATFORM(DOM) - var UID = [[self class] UID], - hasCustomDrawRect = CustomDrawRectViews[UID]; - - if (!hasCustomDrawRect && typeof hasCustomDrawRect === "undefined") - { - hasCustomDrawRect = [self methodForSelector:@selector(drawRect:)] != [CPView instanceMethodForSelector:@selector(drawRect:)]; - CustomDrawRectViews[UID] = hasCustomDrawRect; - } - - if (!hasCustomDrawRect) + if (!(_viewClassFlags & CPViewHasCustomDrawRect)) return; #endif @@ -1680,22 +1697,10 @@ setBoundsOrigin: _needsLayout = YES; #if PLATFORM(DOM) - var UID = [[self class] UID], - hasCustomLayoutSubviews = CustomLayoutSubviewsViews[UID]; - - if (hasCustomLayoutSubviews === undefined) - { - hasCustomLayoutSubviews = [self methodForSelector:@selector(layoutSubviews)] != [CPView instanceMethodForSelector:@selector(layoutSubviews)]; - CustomLayoutSubviewsViews[UID] = hasCustomLayoutSubviews; - } - - if (!hasCustomLayoutSubviews) + if (!(_viewClassFlags & CPViewHasCustomLayoutSubviews)) return; - if (_needsLayout) - { - CPDOMDisplayServerAddView(self); - } + CPDOMDisplayServerAddView(self); #endif } @@ -2335,6 +2340,8 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask", [self setBackgroundColor:[aCoder decodeObjectForKey:CPViewBackgroundColorKey]]; + [self setupViewFlags]; + _theme = [CPTheme defaultTheme]; _themeState = CPThemeState([aCoder decodeIntForKey:CPViewThemeStateKey]); _themeAttributes = {}; @@ -2450,6 +2457,7 @@ var _CPViewGetTransform = function(/*CPView*/ fromView, /*CPView */ toView) { var view = fromView; + // FIXME: This doesn't handle the case when the outside views are equal. // If we have a fromView, "climb up" the view tree until // we hit the root node or we hit the toLayer. while (view && view != toView) diff --git a/AppKit/OLDCPTableColumn.j b/AppKit/OLDCPTableColumn.j new file mode 100644 index 000000000..6d37c1de9 --- /dev/null +++ b/AppKit/OLDCPTableColumn.j @@ -0,0 +1,471 @@ +/* + * CPTableColumn.j + * AppKit + * + * Created by Francisco Tolmasky. + * 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
Each CPTableColumn object is identified by a CPString, called the column identifier. The reason is that, after a column has been added to a table view, the user might move the columns around, so there is a need to identify the columns regardless of its position in the table. + + @ignore +*/ +@implementation CPTableColumn : CPObject +{ + CPString _identifier; + CPView _headerView; + + CPTableView _tableView; + + float _width; + float _minWidth; + float _maxWidth; + + unsigned _resizingMask; + + CPView _dataView; // default data view for this column + + Object _dataViewData; // cache of data view archives (key=data view UID, value=data view archive) + Object _dataViewForView; // mapping from view instances back to their data view prototype (key=view instance UID, value=data view) + Object _purgableInfosForDataView; // (key=data view UID, value=) +} + +/*! + Initializes the table column with the specified identifier. + @param anIdentifier the identifier + @return the initialized table column +*/ +- (id)initWithIdentifier:(CPString)anIdentifier +{ + self = [super init]; + + if (self) + { + [self _init]; + + _identifier = anIdentifier; + + _width = 40.0; + _minWidth = 8.0; + _maxWidth = 1000.0; + + var dataView = [[CPTextField alloc] initWithFrame:CPRectMakeZero()]; + [dataView setValue:[CPColor whiteColor] forThemeAttribute:"text-color" inState:CPThemeStateHighlighted]; + + [self setDataView:dataView]; + + _headerView = [[CPTextField alloc] initWithFrame:CPRectMakeZero()]; + [_headerView setBackgroundColor:[CPColor greenColor]]; + } + + return self; +} + +- (void)_init +{ + _dataViewData = {}; + _dataViewForView = {}; + _purgableInfosForDataView = {}; +} + +/*! + Sets the table column's identifier + @param anIdentifier the new identifier +*/ +- (void)setIdentifier:(CPString)anIdentifier +{ + _identifier = anIdentifier; +} + +/*! + Returns the table column's identifier +*/ +- (CPString)identifier +{ + return _identifier; +} + +// Setting the CPTableView +/*! + Sets the table's view. This is called automatically by Cappuccino. + @param aTableView the new table view +*/ +- (void)setTableView:(CPTableView)aTableView +{ + _tableView = aTableView; +} + +/*! + Returns the column's table view. +*/ +- (CPTableView)tableView +{ + return _tableView; +} + +// Controlling size +/*! + Sets the column's width. + @param aWidth the new column width +*/ +- (void)setWidth:(float)aWidth +{ + _width = aWidth; +} + +/*! + Returns the column's width +*/ +- (float)width +{ + return _width; +} + +/*! + Sets column's minimum width. + @param aWidth the new minimum column width +*/ +- (void)setMinWidth:(float)aWidth +{ + if (_width < (_minWidth = aWidth)) + [self setWidth:_minWidth]; +} + +/*! + The column's minimum width +*/ +- (float)minWidth +{ + return _minWidth; +} + +/*! + Sets the column's maximum width. + @param aWidth the new maximum width +*/ +- (void)setMaxWidth:(float)aWidth +{ + if (_width > (_maxmimumWidth = aWidth)) + [self setWidth:_maxWidth]; +} + +/*! + Sets the resizing mask. The mask is one of: +
+CPTableColumnNoResizing; +CPTableColumnAutoresizingMask; +CPTableColumnUserResizingMask; ++ @param aMask the new resizing mask +*/ +- (void)setResizingMask:(unsigned)aMask +{ + _resizingMask = aMask; +} + +/*! + Returns the column's resizing mask. One of: +
+CPTableColumnNoResizing; +CPTableColumnAutoresizingMask; +CPTableColumnUserResizingMask; ++*/ +- (unsigned)resizingMask +{ + return _resizingMask; +} + +/*! + Resizes the column according to the min, max and set width. +*/ +- (void)sizeToFit +{ + var width = CPRectGetWidth([_headerView frame]); + + if (width < _minWidth) + [self setMinWidth:width]; + else if (width > _maxWidth) + [self setMaxWidth:width] + + if (_width != width) + [self setWidth:width]; +} + +/*! + Sets whether the column in this data is editable. + @param aFlag
YES means the column data is editable
+*/
+- (void)setEditable:(BOOL)aFlag
+{
+ _isEditable = aFlag;
+}
+
+/*!
+ Returns YES if the column data is editable.
+*/
+- (BOOL)isEditable
+{
+ return _isEditable;
+}
+
+//Setting the column header view
+
+/*!
+ Sets the view that draws the column's header.
+ @param aHeaderView the view that will draws the column header
+*/
+
+- (void)setHeaderView:(CPView)aView
+{
+ _headerView = aView;
+}
+
+/*!
+ Return the view that draws the column's header
+*/
+- (CPView)headerView
+{
+ return _headerView;
+}
+
+/*!
+ Sets the data cell that draws rows in this column.
+*/
+- (void)setDataCell:(CPView dataCell. This can
+ be overridden by a subclass to return different cells for different
+ rows.
+ @param aRowIndex the index of the row to obtain the cell for
+*/
+- (CPCell)dataCellForRow:(int)aRowIndex
+{
+ return [self dataView];
+}
+
+- (CPView)dataViewForRow:(int)aRowIndex
+{
+ return [self dataCellForRow:aRowIndex];
+}
+/*
+- (void)_markViewAsPurgable:(CPView)aView
+{
+ var viewUID = [aView UID],
+ dataViewUID = [_dataViewForView[viewUID] UID];
+
+ if (!_purgableInfosForDataView[dataViewUID])
+ _purgableInfosForDataView[dataViewUID] = [CPDictionary dictionary];
+
+ [_purgableInfosForDataView[dataViewUID] setObject:aView forKey:viewUID];
+}
+*/
+- (void)_markView:(CPView)aView inRow:(unsigned)aRow asPurgable:(BOOL)isPurgable
+{
+ var viewUID = [aView UID],
+ dataViewUID = [_dataViewForView[viewUID] UID];
+
+ if (!_purgableInfosForDataView[dataViewUID])
+ {
+ if (!isPurgable)
+ return;
+
+ _purgableInfosForDataView[dataViewUID] = {};
+ }
+
+ if (!isPurgable) {
+ if (_purgableInfosForDataView[dataViewUID][viewUID])
+ CPLog.warn("removing unpurgable " + _purgableInfosForDataView[dataViewUID][viewUID]);
+ delete _purgableInfosForDataView[dataViewUID][viewUID];
+ }
+ else
+ _purgableInfosForDataView[dataViewUID][viewUID] = PurgableInfoMake(aView, aRow);
+}
+
+- (CPView)_newDataViewForRow:(int)aRowIndex avoidingRows:(CPRange)rows
+{
+ var view = [self dataViewForRow:aRowIndex],
+ viewUID = [view UID],
+ purgableInfos = _purgableInfosForDataView[viewUID];
+
+ if (purgableInfos)
+ {
+ for (var key in purgableInfos)
+ {
+ var info = purgableInfos[key];
+ //if (!CPLocationInRange(PurgableInfoRow(info), rows))
+ //{
+ //CPLog.debug("yes, a purged view is usable, its called: " + PurgableInfoView(info));
+ delete purgableInfos[key];
+ return PurgableInfoView(info);
+ //}
+ //else
+ // CPLog.warn("avoiding");
+ }
+ }
+
+ // if we haven't cached an archive of the data view, do it now
+ if (!_dataViewData[viewUID])
+ _dataViewData[viewUID] = [CPKeyedArchiver archivedDataWithRootObject:view];
+
+ // unarchive the data view cache
+ var newView = [CPKeyedUnarchiver unarchiveObjectWithData:_dataViewData[viewUID]];
+
+ // map the new view's UID to it's data view prototype
+ _dataViewForView[[newView UID]] = view;
+
+ CPLog.warn("creating cell: %s", newView);
+
+ return newView;
+}
+
+- (void)_purge
+{
+ for (var viewUID in _purgableInfosForDataView)
+ {
+ var purgableInfos = _purgableInfosForDataView[viewUID];
+
+ for (var key in purgableInfos)
+ {
+ var view = PurgableInfoView(purgableInfos[key]);
+
+ if (!view)
+ CPLog.info("key="+key+" view=" + view + " purgableInfos[key]="+purgableInfos[key])
+ else if (view._superview) {
+ //CPLog.error("PURGING: (removing)" + view);
+ //[view removeFromSuperview];
+ [view setHidden:YES];
+ }
+ //else
+ // CPLog.warn("PURGING: (already removed)" + view);
+
+ //delete purgableInfos[key];
+ }
+ }
+}
+
+@end
+
+
+var CPTableColumnIdentifierKey = @"CPTableColumnIdentifierKey",
+ CPTableColumnHeaderViewKey = @"CPTableColumnHeaderViewKey",
+ CPTableColumnDataViewKey = @"CPTableColumnDataViewKey",
+ CPTableColumnWidthKey = @"CPTableColumnWidthKey",
+ CPTableColumnMinWidthKey = @"CPTableColumnMinWidthKey",
+ CPTableColumnMaxWidthKey = @"CPTableColumnMaxWidthKey",
+ CPTableColumnResizingMaskKey = @"CPTableColumnResizingMaskKey";
+
+@implementation CPTableColumn (CPCoding)
+
+- (id)initWithCoder:(CPCoder)aCoder
+{
+ [self _init];
+
+ _identifier = [aCoder decodeObjectForKey:CPTableColumnIdentifierKey];
+
+ [self setHeaderView:[aCoder decodeObjectForKey:CPTableColumnHeaderViewKey]];
+ [self setDataView:[aCoder decodeObjectForKey:CPTableColumnDataViewKey]];
+
+ _width = [aCoder decodeFloatForKey:CPTableColumnWidthKey];
+ _minWidth = [aCoder decodeFloatForKey:CPTableColumnMinWidthKey];
+ _maxWidth = [aCoder decodeFloatForKey:CPTableColumnMaxWidthKey];
+
+ _resizingMask = [aCoder decodeBoolForKey:CPTableColumnResizingMaskKey];
+
+ return self;
+}
+
+- (void)encodeWithCoder:(CPCoder)aCoder
+{
+ [aCoder encodeObject:_identifier forKey:CPTableColumnIdentifierKey];
+
+ [aCoder encodeObject:_headerView forKey:CPTableColumnHeaderViewKey];
+ [aCoder encodeObject:_dataView forKey:CPTableColumnDataViewKey];
+
+ [aCoder encodeObject:_width forKey:CPTableColumnWidthKey];
+ [aCoder encodeObject:_minWidth forKey:CPTableColumnMinWidthKey];
+ [aCoder encodeObject:_maxWidth forKey:CPTableColumnMaxWidthKey];
+
+ [aCoder encodeObject:_resizingMask forKey:CPTableColumnResizingMaskKey];
+}
+
+@end
diff --git a/AppKit/OLDCPTableView.j b/AppKit/OLDCPTableView.j
new file mode 100644
index 000000000..02b2e5cd9
--- /dev/null
+++ b/AppKit/OLDCPTableView.j
@@ -0,0 +1,1302 @@
+/*
+ * CPTableView.j
+ * AppKit
+ *
+ * Created by Francisco Tolmasky.
+ * 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 "CPControl.j"
+@import "CPTableColumn.j"
+
+@import "CPColor.j"
+@import "CPTextField.j"
+
+#define ROW_HEIGHT(aRow) (_hasVariableHeightRows ? _rowHeights[aRow] : _rowHeight)
+#define ROW_MIN_Y(aRow) (_hasVariableHeightRows ? _rowMinYs[aRow] : (aRow * (_rowHeight + _intercellSpacing.height)))
+
+CPTableViewColumnDidMoveNotification = "CPTableViewColumnDidMoveNotification";
+CPTableViewColumnDidResizeNotification = "CPTableViewColumnDidResizeNotification";
+CPTableViewSelectionDidChangeNotification = "CPTableViewSelectionDidChangeNotification";
+CPTableViewSelectionIsChangingNotification = "CPTableViewSelectionIsChangingNotification";
+
+var _CPTableViewWillDisplayCellSelector = 1 << 0,
+ _CPTableViewShouldSelectRowSelector = 1 << 1,
+ _CPTableViewShouldSelectTableColumnSelector = 1 << 2,
+ _CPTableViewSelectionShouldChangeSelector = 1 << 3,
+ _CPTableViewShouldEditTableColumnSelector = 1 << 4,
+ _CPTableViewSelectionIndexesForProposedSelectionSelector = 1 << 5,
+ _CPTableViewHeightOfRowSelector = 1 << 6;
+
+/*
+ @ingroup appkit
+
+ This class is not yet stable.
+
+ CPTableView is located within the AppKit framework and is used to display tables.
+ It uses a delegate model for getting its data i.e. you give it an object that provides
+ it with the data it should display.
+
+ @ignore
+*/
+@implementation CPTableView : CPControl
+{
+ // Archived:
+
+ id _dataSource;
+ id _delegate;
+
+ //CPTableHeaderView _headerView;
+ //CPView _cornerView;
+
+ CPArray _tableColumns;
+
+ CPIndexSet _selectedRowIndexes;
+ //CPArray _selectedColumns;
+
+ float _rowHeight;
+ CGSize _intercellSpacing;
+
+ BOOL _allowsMultipleSelection;
+ BOOL _allowsEmptySelection;
+ //BOOL _allowsColumnReordering;
+ //BOOL _allowsColumnResizing;
+ //BOOL _allowsColumnSelection;
+ //BOOL _autoresizesAllColumnsToFit;
+
+ //CPColor _gridColor;
+ //BOOL _drawsGrid;
+
+ SEL _doubleAction;
+
+ // Not archived:
+
+ int _delegateSelectorsCache;
+
+ //CPScrollView _scrollView;
+
+ unsigned _numberOfRows;
+ //unsigned _numberOfColumns;
+
+ BOOL _hasVariableHeightRows;
+
+ // Heights
+ float _columnHeight; // calculated
+
+ CPArray _rowHeights;
+ CPArray _rowMinYs;
+
+ CPArray _tableCells;
+ CPArray _tableColumnViews;
+
+ // Caching
+ Object _dataViewCache;
+ CPArray _objectValueCache;
+
+ CPRange _visibleRows;
+ CPRange _visibleColumns;
+
+ CPRange _populatedRows;
+ CPRange _populatedColumns;
+
+ // Selection
+ CPIndexSet _previousSelectedRowIndexes;
+ int _selectionStartRow;
+ int _selectionModifier;
+
+ CPIndexSet _currentlySelected;
+ CPArray _selectionViews;
+ CPArray _selectionViewsPool;
+
+ CPDate _scrollTimer;
+}
+
++ (void)initialize
+{
+
+}
+
+- (id)initWithFrame:(CGRect)aFrame
+{
+ self = [super initWithFrame:aFrame];
+
+ if (self)
+ {
+ [self _init];
+ }
+
+ return self;
+}
+
+- (void)_init
+{
+ _tableColumns = [];
+ //_numberOfColumns = 0;
+
+ _selectedRowIndexes = [CPIndexSet indexSet];
+
+ _rowHeight = 17.0;
+ _intercellSpacing = CPSizeMake(3.0, 2.0);
+
+ _allowsMultipleSelection = YES;
+ _allowsEmptySelection = YES;
+
+
+ _tableCells = [];
+ _tableColumnViews = [];
+
+ _dataViewCache = {};
+ _objectValueCache = [];
+
+ _visibleRows = CPMakeRange(0, 0);
+ _visibleColumns = CPMakeRange(0, 0);
+
+ _rowHeights = [];
+ _rowMinYs = [];
+}
+
+/*
+ Returns the table's column height
+*/
+- (float)_columnHeight
+{
+ return _columnHeight;//_numberOfRows * (_rowHeight + _intercellSpacing.height);
+}
+
+- (void)newCellForRow:(unsigned)aRowIndex column:(unsigned)aColumnIndex avoidingRows:(CPRange)rows
+{
+ var dataView = [_tableColumns[aColumnIndex] _newDataViewForRow:aRowIndex avoidingRows:rows];
+
+ [dataView setFrame:CGRectMake(0.0, ROW_MIN_Y(aRowIndex), [_tableColumns[aColumnIndex] width], ROW_HEIGHT(aRowIndex))];
+
+ if ([dataView respondsToSelector:@selector(highlight:)])
+ [dataView highlight:[_selectedRowIndexes containsIndex:aRowIndex]];
+
+ if (!_objectValueCache[aColumnIndex])
+ _objectValueCache[aColumnIndex] = [];
+
+ // We may be storing 0 after all!
+ if (_objectValueCache[aColumnIndex][aRowIndex] === undefined)
+ _objectValueCache[aColumnIndex][aRowIndex] = [_dataSource tableView:self objectValueForTableColumn:_tableColumns[aColumnIndex] row:aRowIndex];
+
+ [dataView setObjectValue:_objectValueCache[aColumnIndex][aRowIndex]];
+
+ return dataView;
+}
+
+- (void)clearCells
+{
+ var columnEnd = CPMaxRange(_visibleColumns),
+ rowEnd = CPMaxRange(_visibleRows);
+
+ for (var column = _visibleColumns.location; column < columnEnd; column++)
+ {
+ var tableColumn = _tableColumns[column],
+ tableColumnCells = _tableCells[column];
+
+ for (var row = _visibleRows.location; row < rowEnd; row++)
+ {
+ var cell = tableColumnCells[row];
+ if (cell)
+ {
+ tableColumnCells[row] = nil;
+ [tableColumn _markView:cell inRow:row asPurgable:YES];
+ }
+ else
+ {
+ CPLog.warn("Missing cell? " + row + "," + column);
+ }
+ }
+ }
+
+ _visibleColumns = CPMakeRange(0,0);
+ _visibleRows = CPMakeRange(0,0);
+}
+
+- (void)loadTableCellsInRect:(CGRect)aRect
+{
+ if (!_dataSource)
+ return;
+
+ // Determine new visible rows and columns.
+
+ var rowStart = MAX(0, [self _rowAtY:CGRectGetMinY(aRect)] - 1),
+ rowEnd = MIN(_numberOfRows, [self _rowAtY:CGRectGetMaxY(aRect)] + 1),
+
+ visibleRows = CPMakeRange(rowStart, rowEnd - rowStart),
+
+ columnStart = MAX(0, [self _columnAtX:CGRectGetMinX(aRect)]),
+ columnEnd = MIN(_tableColumns.length, [self _columnAtX:CGRectGetMaxX(aRect)] + 1),
+
+ visibleColumns = CPMakeRange(columnStart, columnEnd - columnStart);
+
+ if (CPEqualRanges(_visibleRows, visibleRows) && CPEqualRanges(_visibleColumns, visibleColumns))
+ return;
+
+ var unionVisibleRows = CPUnionRange(_visibleRows, visibleRows),
+ unionVisibleColumns = CPUnionRange(_visibleColumns, visibleColumns);
+
+
+ // Determine whether to use 2 sweeps or one. If we have lots of overlap of cells, use just one.
+ if (unionVisibleRows.length * unionVisibleColumns.length <=
+ (_visibleRows.length * _visibleColumns.length) + (visibleRows.length * visibleColumns.length))
+ {
+ //CPLog.info("single sweep");
+
+ var cEnd = CPMaxRange(unionVisibleColumns),
+ rEnd = CPMaxRange(unionVisibleRows),
+ cell;
+
+ for (var column = unionVisibleColumns.location; column < cEnd; ++column)
+ {
+ var tableColumn = _tableColumns[column],
+ tableColumnCells = _tableCells[column],
+ columnIsVisible = CPLocationInRange(column, visibleColumns),
+ newCells = [];
+
+ for (var row = unionVisibleRows.location; row < rEnd; ++row)
+ {
+ if (cell = tableColumnCells[row])
+ {
+ if (!columnIsVisible || !CPLocationInRange(row, visibleRows))
+ {
+ tableColumnCells[row] = nil;
+ [tableColumn _markView:cell inRow:row asPurgable:YES];
+ }
+ }
+ else
+ {
+ newCells.push(row);
+ }
+ }
+
+ while (newCells.length > 0)
+ {
+ var row = newCells.pop();
+
+ tableColumnCells[row] = [self newCellForRow:row column:column avoidingRows:visibleRows];
+
+ if (!tableColumnCells[row]._superview)
+ [_tableColumnViews[column] addSubview:tableColumnCells[row]];
+ else if (tableColumnCells[row]._isHidden)
+ [tableColumnCells[row] setHidden:NO];
+ }
+
+ [tableColumn _purge];
+ }
+ }
+ else {
+ //CPLog.info("double sweep");
+
+ // first sweep: remove old cells
+
+ var cEnd = CPMaxRange(_visibleColumns),
+ rEnd = CPMaxRange(_visibleRows),
+ cell;
+
+ for (var column = _visibleColumns.location; column < cEnd; ++column)
+ {
+ var tableColumn = _tableColumns[column],
+ tableColumnCells = _tableCells[column],
+ columnIsVisible = CPLocationInRange(column, visibleColumns);
+
+ for (var row = _visibleRows.location; row < rEnd; ++row)
+ {
+ if (cell = tableColumnCells[row])
+ {
+ if (!columnIsVisible || !CPLocationInRange(row, visibleRows))
+ {
+ tableColumnCells[row] = nil;
+ [tableColumn _markView:cell inRow:row asPurgable:YES];
+ }
+ }
+ }
+ }
+
+ // second sweep: add new cells
+
+ var cEnd = CPMaxRange(visibleColumns),
+ rEnd = CPMaxRange(visibleRows);
+
+ for (var column = visibleColumns.location; column < cEnd; ++column)
+ {
+ var tableColumn = _tableColumns[column],
+ tableColumnCells = _tableCells[column];
+
+ for (var row = visibleRows.location; row < rEnd; ++row)
+ {
+ tableColumnCells[row] = [self newCellForRow:row column:column avoidingRows:visibleRows];
+
+ if (!tableColumnCells[row]._superview)
+ [_tableColumnViews[column] addSubview:tableColumnCells[row]];
+ else if (tableColumnCells[row]._isHidden)
+ [tableColumnCells[row] setHidden:NO];
+ }
+
+ [tableColumn _purge];
+ }
+ }
+
+
+ _visibleRows = visibleRows;
+ _visibleColumns = visibleColumns;
+
+}
+
+// Setting display attributes
+/*
+ Sets the width and height between cell. The default is (3.0, 2.0) (width, height).
+ @param the width and height spacing
+*/
+- (void)setIntercellSpacing:(CGSize)aSize
+{
+ if (_intercellSpacing.width != aSize.width)
+ {
+ var i = 1,
+ delta = aSize.width - _intercellSpacing.width;
+ total = delta;
+
+ for (; i < _tableColumns.length; ++i, total += delta)
+ {
+ var origin = [_tableColumnViews[i] frame].origin;
+ [_tableColumnViews[i] setFrameOrigin:CGPointMake(origin.x + total, origin.y)];
+ }
+ }
+
+ if (_intercellSpacing.height != aSize.height)
+ {
+ var i = 0;
+
+ for (; i < _tableColumns.length; ++i, total += delta)
+ {
+ [_tableColumnViews[i] setFrameSize:CGSizeMake([_tableColumnViews[i] width], _numberOfRows * (_rowHeight + _intercellSpacing.height))];
+
+ var j = 1,
+ y = _rowHeight + _intercellSpacing.height;
+
+ for (; j < _numberOfRows; ++i, y += _rowHeight + _intercellSpacing.height)
+ {
+ if (!_tableCells[i][j])
+ continue;
+
+ [_tableCells[i][j] setFrameOrigin:CPPointMake(0.0, y)];
+ }
+ }
+
+ // FIXME: variable height rows
+ }
+
+ _intercellSpacing = CPSizeCreateCopy(aSize);
+}
+
+/*
+ Returns the width and height of the space between
+ cells.
+*/
+- (CGSize)intercellSpacing
+{
+ return _intercellSpacing;
+}
+
+/*
+ Sets the height of table rows
+ @param aRowHeight the new height of the table rows
+*/
+- (void)setRowHeight:(unsigned)aRowHeight
+{
+ if (_rowHeight == aRowHeight)
+ return;
+
+ _rowHeight = aRowHeight;
+
+ // don't perform adjustments if we're using variable height rows
+ if (_hasVariableHeightRows)
+ return;
+
+ for (var row = 0; row < _numberOfRows; ++row)
+ for (var column = 0; column < _tableColumns.length; ++column)
+ [_tableCells[column][row] setFrameOrigin:CPPointMake(0.0, row * (_rowHeight + _intercellSpacing.height))];
+}
+
+/*
+ Returns the height of a table row
+*/
+- (unsigned)rowHeight
+{
+ return _rowHeight;
+}
+
+/*
+ Adds a column to the table
+ @param aTableColumn the column to be added
+*/
+- (void)addTableColumn:(CPTableColumn)aTableColumn
+{
+ var i = 0,
+ x = _tableColumns.length ? CPRectGetMaxX([self rectOfColumn:_tableColumns.length - 1]) + _intercellSpacing.width : 0.0,
+ tableColumnView = [[CPView alloc] initWithFrame:CPRectMake(x, 0.0, [aTableColumn width], [self _columnHeight])],
+ tableColumnCells = [];
+
+ [_tableColumns addObject:aTableColumn];
+ [_tableColumnViews addObject:tableColumnView];
+
+// [tableColumnView setBackgroundColor:[CPColor greenColor]];
+
+ [self addSubview:tableColumnView];
+
+ [_tableCells addObject:tableColumnCells];
+
+ // TODO: do we really need to initialize this, or is undefined good enough?
+ for (; i < _numberOfRows; ++i)
+ _tableCells[_tableColumns.length-1][i] = nil;
+
+ //++_numberOfColumns;
+}
+
+/*
+ Removes a column from the table
+ @param aTableColumn the column to remove
+*/
+- (void)removeTableColumn:(CPTableColumn)aTableColumn
+{
+ var frame = [self frame],
+ width = [aTableColumn width] + _intercellSpacing.width,
+ index = [_tableColumns indexOfObjectIdenticalTo:aTableColumn];
+
+ // Remove the column view and all the cell views from the view hierarchy.
+ [_tableColumnViews[i] removeFromSuperview];
+
+ [_tableCells removeObjectAtIndex:index];
+ [_tableColumns removeObjectAtIndex:index];
+ [_tableColumnViews removeObjectAtIndex:index];
+
+ // Shift remaining column views to the left.
+ for (; index < _tableColumns.length; ++ index)
+ [_tableColumnViews[index] setFrameOrigin:CPPointMake(CPRectGetMinX([_tableColumnViews[index] frame]) - width, 0.0)]
+
+ // Resize ourself.
+ [self setFrameSize:CPSizeMake(CPRectGetWidth(frame) - width, CPRectGetHeight(frame))];
+}
+
+/*
+ Not yet implemented. Changes the position of the column in
+ the table.
+ @param fromIndex the original index of the column
+ @param toIndex the desired index of the column
+*/
+- (void)moveColumn:(unsigned)fromIndex toColumn:(unsinged)toIndex
+{
+ if (fromIndex == toIndex)
+ return;
+// FIXME: IMPLEMENT
+}
+
+/*
+ Returns an array containing the table's CPTableColumns.
+*/
+- (CPArray)tableColumns
+{
+ return _tableColumns;
+}
+
+/*
+ Returns the first CPTableColumn equal to the given object (as determined by the "isEqual:" selector), or nil if none exists.
+ @param anObject the object to look for
+*/
+- (CPTableColumn)tableColumnWithIdentifier:(id)anObject
+{
+ for (var i = 0; i < _tableColumns.length; i++)
+ if ([_tableColumns[i] isEqual:anObject])
+ return _tableColumns[i];
+ return nil;
+}
+
+// Getting the dimensions of the table
+/*
+ Returns the number of columns in the table
+*/
+- (int)numberOfColumns
+{
+ return _tableColumns.length;
+}
+
+/*
+ Returns the number of rows in the table
+*/
+- (int)numberOfRows
+{
+ return _numberOfRows;
+}
+
+/*
+ Adjusts the size of the table and it's header view.
+*/
+- (void)tile
+{
+ var HEIGHT = 10.0;
+
+// [_headerView setFrame:CPRectMake(0.0, 0.0, CPRectGetWidth([self bounds]), HEIGHT)];
+// [_scrollView setFrame:CPRectMake(0.0, HEIGHT, CPRectGetWidth([self bounds]), CPRectGetHeight([self bounds]) - HEIGHT)];
+}
+
+/*
+ Sets the object that is used to obtain the table data. The data source must implement the following
+ methods:
++- (int)numberOfRowsInTableView:(CPTableView)aTableView +- (id)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)aTableColumn row:(int)rowIndex ++ @param aDataSource the object with the table data + @throws CPInternalInconsistencyException if
aDataSource doesn't implement all the required methods
+*/
+- (void)setDataSource:(id)aDataSource
+{
+ if (![aDataSource respondsToSelector:@selector(numberOfRowsInTableView:)])
+ [CPException raise:CPInternalInconsistencyException reason:"Data source doesn't support 'numberOfRowsInTableView:'"];
+ if (![aDataSource respondsToSelector:@selector(tableView:objectValueForTableColumn:row:)])
+ [CPException raise:CPInternalInconsistencyException reason:"Data source doesn't support 'tableView:objectValueForTableColumn:row:'"];
+
+ _dataSource = aDataSource;
+
+ [self reloadData];
+}
+
+/*
+ Returns the object that has access to the table data
+*/
+- (id)dataSource
+{
+ return _dataSource;
+}
+
+
+- (id)delegate
+{
+ return _delegate;
+}
+
+
+/*!
+ Sets the delegate for the tableview.
+*/
+- (void)setDelegate:(id)aDelegate
+{
+ if (_delegate === aDelegate)
+ return;
+
+ var notificationCenter = [CPNotificationCenter defaultCenter];
+
+ if ([_delegate respondsToSelector:@selector(tableViewColumnDidMove:)])
+ [notificationCenter removeObserver:_delegate name:CPTableViewColumnDidMoveNotification object:self];
+ if ([_delegate respondsToSelector:@selector(tableViewColumnDidResize:)])
+ [notificationCenter removeObserver:_delegate name:CPTableViewColumnDidResizeNotification object:self];
+ if ([_delegate respondsToSelector:@selector(tableViewSelectionDidChange:)])
+ [notificationCenter removeObserver:_delegate name:CPTableViewSelectionDidChangeNotification object:self];
+ if ([_delegate respondsToSelector:@selector(tableViewSelectionIsChanging:)])
+ [notificationCenter removeObserver:_delegate name:CPTableViewSelectionIsChangingNotification object:self];
+
+ _delegate = aDelegate;
+
+ if ([_delegate respondsToSelector:@selector(tableViewColumnDidMove:)])
+ [notificationCenter addObserver:_delegate selector:@selector(tableViewColumnDidMove:) name:CPTableViewColumnDidMoveNotification object:self];
+ if ([_delegate respondsToSelector:@selector(tableViewColumnDidResize:)])
+ [notificationCenter addObserver:_delegate selector:@selector(tableViewColumnDidResize:) name:CPTableViewColumnDidResizeNotification object:self];
+ if ([_delegate respondsToSelector:@selector(tableViewSelectionDidChange:)])
+ [notificationCenter addObserver:_delegate selector:@selector(tableViewSelectionDidChange:) name:CPTableViewSelectionDidChangeNotification object:self];
+ if ([_delegate respondsToSelector:@selector(tableViewSelectionIsChanging:)])
+ [notificationCenter addObserver:_delegate selector:@selector(tableViewSelectionIsChanging:) name:CPTableViewSelectionIsChangingNotification object:self];
+
+ _delegateSelectorsCache = 0;
+
+ if ([_delegate respondsToSelector:@selector(tableView:willDisplayCell:forTableColumn:row:)])
+ _delegateSelectorsCache |= _CPTableViewWillDisplayCellSelector;
+ if ([_delegate respondsToSelector:@selector(tableView:shouldSelectRow:)])
+ _delegateSelectorsCache |= _CPTableViewShouldSelectRowSelector;
+ if ([_delegate respondsToSelector:@selector(tableView:shouldSelectTableColumn:)])
+ _delegateSelectorsCache |= _CPTableViewShouldSelectTableColumnSelector;
+ if ([_delegate respondsToSelector:@selector(selectionShouldChangeInTableView:)])
+ _delegateSelectorsCache |= _CPTableViewSelectionShouldChangeSelector;
+ if ([_delegate respondsToSelector:@selector(tableView:shouldEditTableColumn:row:)])
+ _delegateSelectorsCache |= _CPTableViewShouldEditTableColumnSelector;
+ if ([_delegate respondsToSelector:@selector(tableView:selectionIndexesForProposedSelection:)])
+ _delegateSelectorsCache |= _CPTableViewSelectionIndexesForProposedSelectionSelector;
+ if ([_delegate respondsToSelector:@selector(tableView:heightOfRow:)])
+ {
+ _delegateSelectorsCache |= _CPTableViewHeightOfRowSelector;
+ _hasVariableHeightRows = YES;
+ }
+ else
+ _hasVariableHeightRows = NO;
+}
+
+
+/*
+ Tells the table view that the number of rows in the table
+ has changed.
+*/
+- (void)noteNumberOfRowsChanged
+{
+ var numberOfRows = [_dataSource numberOfRowsInTableView:self];
+
+ if (_numberOfRows != numberOfRows)
+ {
+ _numberOfRows = numberOfRows;
+
+ [self _recalculateColumnHeight];
+ }
+}
+
+- (void)noteHeightOfRowsWithIndexesChanged:(CPIndexSet)indexSet
+{
+ // FIXME: more efficient version is possible since we know which indexes changes
+ [self _recalculateColumnHeight];
+}
+
+/*
+ Returns the rectangle bounding the specified row.
+ @param aRowIndex the row to obtain a rectangle for
+ @return the bounding rectangle
+*/
+- (CGRect)rectOfRow:(int)aRowIndex
+{
+ return CPRectMake(0.0, ROW_MIN_Y(aRowIndex), CPRectGetWidth([self bounds]), ROW_HEIGHT(aRowIndex));
+}
+
+/*
+ Returns the rectangle bounding the specified column
+ @param aColumnIndex the column to obtain a rectangle for
+ @return the bounding column
+*/
+- (CGRect)rectOfColumn:(int)aColumnIndex
+{
+ return [_tableColumnViews[aColumnIndex] frame];
+}
+
+/*
+ Adjusts column widths to make them all visible at once. Same as tile.
+*/
+- (void)sizeToFit
+{
+// [self tile];
+}
+
+- (void)_recalculateColumnHeight
+{
+ var oldColumnHeight = _columnHeight;
+
+ if (_hasVariableHeightRows)
+ {
+ _rowMinYs[0] = 0;
+ for (var row = 0; row < _numberOfRows; row++)
+ {
+ _rowHeights[row] = [_delegate tableView:self heightOfRow:row];
+ _rowMinYs[row+1] = _rowMinYs[row] + _rowHeights[row] + _intercellSpacing.height;
+ }
+ _columnHeight = _rowMinYs[_numberOfRows]; // last index is one more than last row, and is the total column height
+ }
+ else
+ _columnHeight = _numberOfRows * (_rowHeight + _intercellSpacing.height);
+
+ var count = _tableColumnViews.length;
+
+ while (count--)
+ [_tableColumnViews[count] setFrameSize:CGSizeMake([_tableColumns[count] width], _columnHeight)];
+
+ [self setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), _columnHeight)];
+}
+
+- (CGRect)visibleRectInParent
+{
+ var superview = [self superview];
+
+ if (!superview)
+ return [self bounds];
+
+ return [self convertRect:CGRectIntersection([superview bounds], [self frame]) fromView:superview];
+}
+
+/*
+ Reloads the data from the dataSource. This is an
+ expensive method, so use it lightly.
+*/
+- (void)reloadData
+{
+ var oldNumberOfRows = _numberOfRows;
+
+ _numberOfRows = [_dataSource numberOfRowsInTableView:self];
+
+ if (oldNumberOfRows != _numberOfRows)
+ {
+ [self _recalculateColumnHeight];
+ [self setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), [self _columnHeight])];
+ }
+
+ _objectValueCache = [];
+
+ [self clearCells];
+
+ [self setNeedsLayout];
+}
+
+- (void)layoutSubviews
+{
+ [self loadTableCellsInRect:[self visibleRectInParent]];
+}
+
+- (void)displaySoon
+{
+ [_scrollTimer invalidate];
+ _scrollTimer = [CPTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(displayNow) userInfo:nil repeats:NO];
+}
+
+- (void)displayNow
+{
+ [self setNeedsLayout];
+}
+
+- (void)viewDidMoveToSuperview
+{
+ [[[self enclosingScrollView] contentView] setPostsBoundsChangedNotifications:YES];
+
+ [[CPNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(viewBoundsChanged:)
+ name:CPViewBoundsDidChangeNotification
+ object:[[self enclosingScrollView] contentView]];
+}
+
+- (void)viewBoundsChanged:(CPNotification)aNotification
+{
+ //CPLog.info(_cmd + CPStringFromRect([[[self enclosingScrollView] contentView] bounds]));
+ //objj_debug_print_backtrace();
+ //[self setNeedsLayout];
+ [self displayNow];
+}
+
+/*
+- (void)setAllowsColumnReordering:(BOOL)allowsColumnReordering
+{
+ if (_allowsColumnReordering === _allowsColumnReordering)
+ return;
+
+ _allowsColumnReordering = allowsColumnReordering;
+}
+- (void)allowsColumnReordering
+{
+ return _allowsColumnReordering;
+}
+
+- (void)setAllowsColumnResizing:(BOOL)allowsColumnResizing
+{
+ if (_allowsColumnResizing === allowsColumnResizing)
+ return;
+
+ _allowsColumnResizing = allowsColumnResizing;
+}
+- (void)allowsColumnResizing
+{
+ return _allowsColumnResizing;
+}
+
+- (void)setAllowsColumnSelection:(BOOL)allowsColumnSelection
+{
+ if (_allowsColumnSelection === allowsColumnSelection)
+ return;
+
+ _allowsColumnSelection = allowsColumnSelection;
+}
+- (void)allowsColumnSelection
+{
+ return _allowsColumnSelection;
+}
+*/
+
+- (void)setAllowsMultipleSelection:(BOOL)allowsMultipleSelection
+{
+ if (_allowsMultipleSelection === allowsMultipleSelection)
+ return;
+
+ _allowsMultipleSelection = allowsMultipleSelection;
+
+ // TODO: more stuff?
+}
+- (void)allowsMultipleSelection
+{
+ return _allowsMultipleSelection;
+}
+
+- (void)setAllowsEmptySelection:(BOOL)allowsEmptySelection
+{
+ if (_allowsEmptySelection === allowsEmptySelection)
+ return;
+
+ _allowsEmptySelection = allowsEmptySelection;
+}
+- (void)allowsEmptySelection
+{
+ return _allowsEmptySelection;
+}
+
+
+/*
+ Returns the index of the row at the given point, or CPNotFound (-1) if it is out of range.
+ @param aPoint the point
+ @return the index of the row at aPoint
+*/
+- (int)rowAtPoint:(CGPoint)aPoint
+{
+ var index = [self _rowAtY:aPoint.y]
+
+ if (index >= 0 && index < _numberOfRows)
+ return index;
+ else
+ return CPNotFound;
+}
+
+- (int)columnAtPoint:(CGPoint)aPoint
+{
+ var index = [self _columnAtX:aPoint.x]
+
+ if (index >= 0 && index < _tableColumns.length)
+ return index;
+ else
+ return CPNotFound;
+}
+
+/*
+ @ignore
+
+ Internal version takes a Y value, returns an index, or -1 if its beyond the min, or numberOfRows if it's beyond the max
+*/
+- (int)_rowAtY:(float)y
+{
+ if (_hasVariableHeightRows)
+ {
+ var a = 0,
+ b = _numberOfRows;
+
+ if (y < _rowMinYs[0])
+ return -1;
+ if (y >= _rowMinYs[_rowMinYs.length-1])
+ return _numberOfRows;
+
+ // binary search
+ while (true)
+ {
+ var half = a + Math.floor((b - a) / 2);
+
+ if (y < _rowMinYs[half])
+ b = half;
+ else if (half < _numberOfRows-1 && y >= _rowMinYs[half+1])
+ a = half;
+ else
+ return half;
+ }
+ }
+ else
+ return FLOOR(y / (_rowHeight + _intercellSpacing.height));
+}
+
+/*
+ @ignore
+
+ Internal version takes a X value, returns an index, or -1 if its beyond the min, or numberOfColumns if it's beyond the max
+*/
+- (int)_columnAtX:(float)x
+{
+ var a = 0,
+ b = _tableColumns.length;
+
+ var last = [_tableColumnViews[_tableColumns.length-1] frame];
+ if (x < [_tableColumnViews[0] frame].origin.x)
+ return -1;
+ if (x >= last.origin.x + last.size.width)
+ return _tableColumns.length;
+
+ // binary search
+ while (true)
+ {
+ var half = a + Math.floor((b - a) / 2);
+
+ if (x < [_tableColumnViews[half] frame].origin.x)
+ b = half;
+ else if (half < _tableColumns.length-1 && x >= [_tableColumnViews[half+1] frame].origin.x)
+ a = half;
+ else
+ return half;
+ }
+}
+
+/*
+ Selects the specified row indexes, optionally adding to existing selection
+ @param indexes the indexes to select
+ @param extend whether or not to add to the existing selection
+*/
+- (void)selectRowIndexes:(CPIndexSet)indexes byExtendingSelection:(BOOL)extend
+{
+ // FIXME: should this be subject to the delegate filters, etc?
+
+ if (extend)
+ _selectedRowIndexes = [[_selectedRowIndexes copy] addIndexes:indexes];
+ else if ([indexes count] > 0 || _allowsEmptySelection)
+ _selectedRowIndexes = [indexes copy];
+
+ [self _drawSelection];
+}
+
+/*
+ Returns a CPIndexSet of the selected rows
+ @return indexes of the selected rows
+*/
+- (CPIndexSet)selectedRowIndexes
+{
+ return _selectedRowIndexes;
+}
+
+/*
+ Returns the number of selected rows
+ @return number of selected rows
+*/
+- (int)numberOfSelectedRows
+{
+ return [_selectedRowIndexes count];
+}
+
+
+/*
+ Deselects all rows if allowsEmptySelection is true. If delegate responds to "selectionShouldChangeInTableView:", asks if it should chnage.
+ Sends the CPTableViewSelectionDidChangeNotification on deselection.
+ @param the sender in a target/action
+*/
+- (void)deselectAll:(id)sender
+{
+ if (!_allowsEmptySelection || [_selectedRowIndexes count] === 0 ||
+ ((_delegateSelectorsCache & _CPTableViewSelectionShouldChangeSelector) && ![_delegate selectionShouldChangeInTableView:self]))
+ return;
+
+ [self selectRowIndexes:[CPIndexSet indexSet] byExtendingSelection:NO];
+ [[CPNotificationCenter defaultCenter] postNotificationName:CPTableViewSelectionDidChangeNotification object:self userInfo:nil];
+}
+
+- (void)editColumn:(int)columnIndex row:(int)rowIndex withEvent:(CPEvent)theEvent select:(BOOL)flag
+{
+
+}
+
+/*
+ @ignore
+*/
+- (void)_updateSelectionWithMouseAtRow:(int)aRow
+{
+ // Make a preliminary new selection
+ var newSelection;
+ if (_allowsMultipleSelection)
+ newSelection = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(MIN(aRow, _selectionStartRow), ABS(aRow-_selectionStartRow)+1)];
+ else if (aRow >= 0 && aRow < _numberOfRows)
+ newSelection = [CPIndexSet indexSetWithIndex:aRow];
+ else
+ newSelection = [CPIndexSet indexSet];
+
+ // If cmd/ctrl was held down XOR the old selection with the proposed selection
+ if (_allowsMultipleSelection && _selectionModifier & (CPCommandKeyMask | CPControlKeyMask | CPAlternateKeyMask))
+ {
+ // A = newSelection, B = _previousSelectedRowIndexes
+ // (A intersection B) = (A - (A - B))
+ var intersection = [newSelection copy],
+ difference = [newSelection copy];
+ [difference removeIndexes:_previousSelectedRowIndexes];
+ [intersection removeIndexes:difference]
+
+ // (A xor B) = (A + B) - (A intersection B)
+ [newSelection addIndexes:_previousSelectedRowIndexes];
+ [newSelection removeIndexes:intersection];
+
+ // FIXME: if multiple selection is off, and we cmd/ctrl click the previously selected row, then deselect it.
+ }
+
+ // if the new selection is different than the old selection
+ if (![newSelection isEqualToIndexSet:_selectedRowIndexes])
+ {
+ // ask the delegate if we should change the selection
+ if ((_delegateSelectorsCache & _CPTableViewSelectionShouldChangeSelector) && ![_delegate selectionShouldChangeInTableView:self])
+ return;
+
+ // ask the delegate which indexes can be selected. selectionIndexesForProposedSelection is faster than shouldSelectRow
+ if (_delegateSelectorsCache & _CPTableViewSelectionIndexesForProposedSelectionSelector)
+ newSelection = [_delegate tableView:self selectionIndexesForProposedSelection:newSelection];
+ else if (_delegateSelectorsCache & _CPTableViewShouldSelectRowSelector)
+ {
+ var indexes = [];
+ [newSelection getIndexes:indexes maxCount:Number.MAX_VALUE inIndexRange:nil];
+ for (var i = 0; i < indexes.length; i++)
+ if (![_delegate tableView:self shouldSelectRow:indexes[i]])
+ [newSelection removeIndex:indexes[i]];
+ }
+ }
+
+ // if empty selection is not allowed and the new selection has nothing selected, abort
+ if (!_allowsEmptySelection && [newSelection count] === 0)
+ return;
+
+ // if the new selection is *still* different, and update the selection and send a notification
+ if (![newSelection isEqualToIndexSet:_selectedRowIndexes])
+ {
+ [self selectRowIndexes:newSelection byExtendingSelection:NO];
+ [[CPNotificationCenter defaultCenter] postNotificationName:CPTableViewSelectionIsChangingNotification object:self userInfo:nil];
+ }
+}
+
+/*
+ @ignore
+*/
+- (void)mouseDown:(CPEvent)anEvent
+{
+ [self trackSelection:anEvent];
+}
+
+/*
+ Sets the message to be sent to the target when a cell is double clicked
+ @param aSelector the selector to be performed
+*/
+- (void)setDoubleAction:(SEL)aSelector
+{
+ _doubleAction = aSelector;
+}
+- (SEL)doubleAction
+{
+ return _doubleAction;
+}
+
+- (int)clickedColumn
+{
+ return _clickedColumn;
+}
+- (int)clickedRow
+{
+ return _clickedRow;
+}
+
+/*
+ @ignore
+*/
+- (void)trackSelection:(CPEvent)anEvent
+{
+ var type = [anEvent type],
+ point = [self convertPoint:[anEvent locationInWindow] fromView:nil],
+ currentRow = MAX(0, MIN(_numberOfRows-1, [self _rowAtY:point.y]));
+
+ if (type == CPLeftMouseUp)
+ {
+ _clickedRow = [self rowAtPoint:point];
+ _clickedColumn = [self columnAtPoint:point];
+
+ if ([anEvent clickCount] === 2)
+ {
+ CPLog.warn("edit?!");
+
+ [self sendAction:_doubleAction to:_target];
+ }
+ else
+ {
+ if (![_previousSelectedRowIndexes isEqualToIndexSet:_selectedRowIndexes])
+ {
+ [[CPNotificationCenter defaultCenter] postNotificationName:CPTableViewSelectionDidChangeNotification object:self userInfo:nil];
+ }
+
+ [self sendAction:_action to:_target];
+ }
+
+ return;
+ }
+
+ if (type == CPLeftMouseDown)
+ {
+ _previousSelectedRowIndexes = _selectedRowIndexes;
+ _selectionModifier = [anEvent modifierFlags];
+
+ if (_selectionModifier & CPShiftKeyMask)
+ _selectionStartRow = (ABS([_previousSelectedRowIndexes firstIndex] - currentRow) < ABS([_previousSelectedRowIndexes lastIndex] - currentRow)) ?
+ [_previousSelectedRowIndexes firstIndex] : [_previousSelectedRowIndexes lastIndex];
+ else
+ _selectionStartRow = currentRow;
+
+ [self _updateSelectionWithMouseAtRow:currentRow];
+ }
+ else if (type == CPLeftMouseDragged)
+ {
+ [self _updateSelectionWithMouseAtRow:currentRow];
+ }
+
+ [CPApp setTarget:self selector:@selector(trackSelection:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
+}
+
+/*
+ @ignore
+*/
+- (void)_drawSelection
+{
+ if (!_currentlySelected) {
+ _currentlySelected = [CPIndexSet indexSet];
+ _selectionViews = [];
+ _selectionViewsPool = [];
+ }
+
+ // TODO: we could also remove selections that aren't visible, but then we'll need to run this on every scroll/resize?
+
+ // get array of indexes we can remove
+ var removeSet = [_currentlySelected copy],
+ indexesToRemove = [];
+ [removeSet removeIndexes:_selectedRowIndexes];
+ [removeSet getIndexes:indexesToRemove maxCount:Number.MAX_VALUE inIndexRange:nil];
+
+ // get array of indexes we need to add
+ var addSet = [_selectedRowIndexes copy],
+ indexesToAdd = [];
+ [addSet removeIndexes:_currentlySelected];
+ [addSet getIndexes:indexesToAdd maxCount:Number.MAX_VALUE inIndexRange:nil];
+
+ for (var i = 0; i < indexesToRemove.length; i++)
+ {
+ var row = indexesToRemove[i];
+ for (var column = 0; column < _tableColumns.length; column++)
+ if ([_tableCells[column][row] respondsToSelector:@selector(highlight:)])
+ [_tableCells[column][row] highlight:NO];
+ }
+ for (var i = 0; i < indexesToAdd.length; i++)
+ {
+ var row = indexesToAdd[i];
+ for (var column = 0; column < _tableColumns.length; column++)
+ if ([_tableCells[column][row] respondsToSelector:@selector(highlight:)])
+ [_tableCells[column][row] highlight:YES];
+ }
+
+ // add each one we need to add, taking the selection views from removed seelctions, the pool, or new
+ for (var i = 0; i < indexesToAdd.length; i++)
+ {
+ var index = indexesToAdd[i],
+ view;
+
+ if (indexesToRemove.length > 0)
+ {
+ view = _selectionViews[indexesToRemove.pop()];
+ }
+ else if (_selectionViewsPool.length > 0)
+ {
+ view = _selectionViewsPool.pop();
+ [self addSubview:view positioned:CPWindowBelow relativeTo:nil];
+ }
+ else
+ {
+ view = [[CPView alloc] init];
+ [view setBackgroundColor:[CPColor alternateSelectedControlColor]];
+
+ [self addSubview:view positioned:CPWindowBelow relativeTo:nil];
+ }
+
+ _selectionViews[index] = view;
+
+ var frame = [self rectOfRow:index];
+ frame.size.height += _intercellSpacing.height - 1;
+ //frame.size.width += 500;
+
+ [view setFrame:frame];
+ }
+
+ // remove any selections that weren't already reused
+ for (var i = 0; i < indexesToRemove.length; i++)
+ {
+ var row = indexesToRemove[i],
+ view = _selectionViews[row];
+
+ [view removeFromSuperview];
+ _selectionViewsPool.push(view);
+ }
+
+ // update the currently selected index set
+ _currentlySelected = [_selectedRowIndexes copy];
+}
+
+@end
+
+
+var CPTableViewDataSourceKey = @"CPTableViewDataSourceKey",
+ CPTableViewDelegateKey = @"CPTableViewDelegateKey",
+ CPTableViewHeaderViewKey = @"CPTableViewHeaderViewKey",
+ CPTableViewTableColumnsKey = @"CPTableViewTableColumnsKey",
+ CPTableViewRowHeightKey = @"CPTableViewRowHeightKey",
+ CPTableViewIntercellSpacingKey = @"CPTableViewIntercellSpacingKey",
+ CPTableViewMultipleSelectionKey = @"CPTableViewMultipleSelectionKey",
+ CPTableViewEmptySelectionKey = @"CPTableViewEmptySelectionKey";
+
+@implementation CPTableView (CPCoding)
+
+- (id)initWithCoder:(CPCoder)aCoder
+{
+ if (self = [super initWithCoder:aCoder])
+ {
+ [self _init];
+
+ _dataSource = [aCoder decodeObjectForKey:CPTableViewDataSourceKey];
+ _delegate = [aCoder decodeObjectForKey:CPTableViewDelegateKey];
+
+ _rowHeight = [aCoder decodeFloatForKey:CPTableViewRowHeightKey];
+ _intercellSpacing = [aCoder decodeSizeForKey:CPTableViewIntercellSpacingKey];
+
+ _allowsMultipleSelection = [aCoder decodeBoolForKey:CPTableViewMultipleSelectionKey];
+ _allowsEmptySelection = [aCoder decodeBoolForKey:CPTableViewEmptySelectionKey];
+
+ var tableColumns = [aCoder decodeObjectForKey:CPTableViewTableColumnsKey];
+ for (var i = 0; i < tableColumns.length; i++)
+ [self addTableColumn:tableColumns[i]];
+ }
+
+ return self;
+}
+
+- (void)encodeWithCoder:(CPCoder)aCoder
+{
+ [super encodeWithCoder:aCoder];
+
+ [aCoder encodeObject:_dataSource forKey:CPTableViewDataSourceKey];
+ [aCoder encodeObject:_delegate forKey:CPTableViewDelegateKey];
+
+ [aCoder encodeObject:_tableColumns forKey:CPTableViewTableColumnsKey];
+
+ [aCoder encodeFloat:_rowHeight forKey:CPTableViewRowHeightKey];
+ [aCoder encodeSize:_intercellSpacing forKey:CPTableViewIntercellSpacingKey];
+
+ [aCoder encodeBool:_allowsMultipleSelection forKey:CPTableViewMultipleSelectionKey];
+ [aCoder encodeBool:_allowsEmptySelection forKey:CPTableViewEmptySelectionKey];
+}
+
+@end
+
+
+
+@implementation CPColor (TableView)
+
++ (CPColor)alternateSelectedControlColor
+{
+ return [[CPColor alloc] _initWithRGBA:[0.22, 0.46, 0.84, 1.0]];
+}
+
++ (CPColor)secondarySelectedControlColor
+{
+ return [[CPColor alloc] _initWithRGBA:[0.83, 0.83, 0.83, 1.0]];
+}
+
+@end
diff --git a/AppKit/Themes/Aristo/ThemeDescriptors.j b/AppKit/Themes/Aristo/ThemeDescriptors.j
index bde1704c8..7f747a324 100644
--- a/AppKit/Themes/Aristo/ThemeDescriptors.j
+++ b/AppKit/Themes/Aristo/ThemeDescriptors.j
@@ -203,7 +203,8 @@
[scroller setValue:knobColor forThemeAttribute:@"knob-color" inState:CPThemeStateVertical];
- [scroller setFloatValue:0.1 knobProportion:0.5];
+ [scroller setFloatValue:0.1];
+ [scroller setKnobProportion:0.5];
return scroller;
}
@@ -246,10 +247,11 @@
[_CPCibCustomResource imageResourceWithName:"scroller-horizontal-knob-right.png" size:CGSizeMake(9.0, 15.0)]
]
isVertical:NO]);
-
+
[scroller setValue:knobColor forThemeAttribute:@"knob-color"];
-
- [scroller setFloatValue:0.1 knobProportion:0.5];
+
+ [scroller setFloatValue:0.1];
+ [scroller setKnobProportion:0.5];
return scroller;
}
diff --git a/AppKit/_CPCornerView.j b/AppKit/_CPCornerView.j
new file mode 100644
index 000000000..5a30dfc90
--- /dev/null
+++ b/AppKit/_CPCornerView.j
@@ -0,0 +1,18 @@
+
+@import "CPView.j"
+
+@implementation _CPCornerView : CPView
+{
+}
+
+- (id)initWithFrame:(CGRect)aFrame
+{
+ if (self = [super initWithFrame:aFrame])
+ {
+ [self setBackgroundColor:[CPColor purpleColor]];
+ }
+
+ return self;
+}
+
+@end
diff --git a/Foundation/CPIndexSet.j b/Foundation/CPIndexSet.j
index 932483ca4..ead56ea10 100644
--- a/Foundation/CPIndexSet.j
+++ b/Foundation/CPIndexSet.j
@@ -23,6 +23,9 @@
@import "CPRange.j"
@import "CPObject.j"
+#define _CPMaxRange(aRange) ((aRange).location + (aRange).length)
+#define _CPMakeRange(aLocation, aLength) { location:(aLocation), length:aLength }
+#define _CPMakeRangeCopy(aRange) { location:(aRange).location, length:(aRange).length }
/*!
@class CPIndexSet
@@ -35,7 +38,6 @@
@implementation CPIndexSet : CPObject
{
unsigned _count;
- unsigned _cachedRangeIndex;
CPArray _ranges;
}
@@ -45,7 +47,7 @@
*/
+ (id)indexSet
{
- return [[self alloc] init];
+ return [[self alloc] init];
}
/*!
@@ -53,7 +55,7 @@
*/
+ (id)indexSetWithIndex:(int)anIndex
{
- return [[self alloc] initWithIndex:anIndex];
+ return [[self alloc] initWithIndex:anIndex];
}
/*!
@@ -62,41 +64,23 @@
*/
+ (id)indexSetWithIndexesInRange:(CPRange)aRange
{
- return [[self alloc] initWithIndexesInRange:aRange];
+ return [[self alloc] initWithIndexesInRange:aRange];
}
// Initializing and Index Set
- (id)init
{
- self = [super init];
-
- if (self)
- {
- _count = 0;
- _ranges = [];
- _cachedRangeIndex = 0;
- }
-
- return self;
+ return [self initWithIndexesInRange:_CPMakeRange(0, 0)];
}
/*!
Initializes the index set with a single index.
@return the initialized index set
*/
-- (id)initWithIndex:(int)anIndex
+- (id)initWithIndex:(CPInteger)anIndex
{
- self = [super init];
-
- if (self)
- {
- _count = 1;
- _ranges = [CPArray arrayWithObject:CPMakeRange(anIndex, 1)];
- _cachedRangeIndex = 0;
- }
-
- return self;
+ return [self initWithIndexesInRange:_CPMakeRange(anIndex, 1)];
}
/*!
@@ -107,14 +91,17 @@
- (id)initWithIndexesInRange:(CPRange)aRange
{
self = [super init];
-
+
if (self)
{
- _count = aRange.length;
- _ranges = [CPArray arrayWithObject:aRange];
- _cachedRangeIndex = 0;
+ _count = MAX(0, aRange.length);
+
+ if (_count > 0)
+ _ranges = [aRange];
+ else
+ _ranges = [];
}
-
+
return self;
}
@@ -126,20 +113,19 @@
- (id)initWithIndexSet:(CPIndexSet)anIndexSet
{
self = [super init];
-
+
if (self)
{
_count = [anIndexSet count];
_ranges = [];
- _cachedRangeIndex = 0;
-
- var index = 0,
- count = anIndexSet._ranges.length;
-
- for (; index < count; ++index)
- _ranges.push(CPCopyRange(anIndexSet._ranges[index]));
+
+ var otherRanges = anIndexSet._ranges,
+ otherRangesCount = otherRanges.length;
+
+ while (otherRangesCount--)
+ _ranges[otherRangesCount] = _CPMakeRangeCopy(otherRanges[otherRangesCount]);
}
-
+
return self;
}
@@ -151,24 +137,26 @@
*/
- (BOOL)isEqualToIndexSet:(CPIndexSet)anIndexSet
{
+ if (!anIndexSet)
+ return NO;
+
// Comparisons to ourself are always return YES.
- if (self == anIndexSet)
- return YES;
-
- var i = 0,
- count = _ranges.length;
+ if (self === anIndexSet)
+ return YES;
+
+ var rangesCount = _ranges.length,
otherRanges = anIndexSet._ranges;
-
- // If we have a discrepency in the number of ranges or the number of indexes,
- // simply return NO.
- if (count != otherRanges.length || _count != [anIndexSet count])
- return NO;
-
- for (; i < count; ++i)
- if (!CPEqualRanges(_ranges[i], otherRanges[i]))
- return NO;
-
- return YES;
+
+ // If we have a discrepency in the number of ranges or the number of indexes,
+ // simply return NO.
+ if (rangesCount !== otherRanges.length || _count !== anIndexSet._count)
+ return NO;
+
+ while (rangesCount--)
+ if (!CPEqualRanges(_ranges[rangesCount], otherRanges[rangesCount]))
+ return NO;
+
+ return YES;
}
/*!
@@ -176,9 +164,9 @@
@param anIndex the index to check for in the set
@return \c YES if \c anIndex is in the receiver index set
*/
-- (BOOL)containsIndex:(unsigned)anIndex
+- (BOOL)containsIndex:(CPInteger)anIndex
{
- return [self containsIndexesInRange:CPMakeRange(anIndex, 1)];
+ return positionOfIndex(_ranges, anIndex) !== CPNotFound;
}
/*!
@@ -187,29 +175,24 @@
*/
- (BOOL)containsIndexesInRange:(CPRange)aRange
{
- if(!_count)
+ if (aRange.length <= 0)
return NO;
- var i = SOERangeIndex(self, aRange.location),
- lower = aRange.location,
- upper = CPMaxRange(aRange),
- count = _ranges.length;
+ // If we have less total indexes than aRange, we can't possibly contain aRange.
+ if(_count < aRange.length)
+ return NO;
- // Stop if the location is ever bigger than or equal to our
- // non-inclusive upper bound
- for(;i < count && _ranges[i].location < upper; ++i)
- // The range must be a subset of one our ranges.
- if (_ranges[i].location <= lower && CPMaxRange(_ranges[i]) >= upper)
- {
- _cachedRangeIndex = i;
- return YES;
- }
-
- // The value isn't here, but values greater than anIndex should
- // start from here regardless.
- _cachedRangeIndex = i;
-
- return NO;
+ // Search for first location
+ var rangeIndex = positionOfIndex(_ranges, aRange.location);
+
+ // If we don't have the first location, then we don't contain aRange.
+ if (rangeIndex === CPNotFound)
+ return NO;
+
+ var range = _ranges[rangeIndex];
+
+ // The intersection must contain all the indexes from the original range.
+ return CPIntersectionRange(range, aRange).length === aRange.length;
}
/*!
@@ -218,22 +201,22 @@
*/
- (BOOL)containsIndexes:(CPIndexSet)anIndexSet
{
- // Return YES if anIndexSet has no indexes
- if(![anIndexSet count])
- return YES;
-
- // Return NO if we have no indexes.
- if(!_count)
+ var otherCount = anIndexSet._count;
+
+ if(otherCount <= 0)
+ return YES;
+
+ // If we have less total indexes than anIndexSet, we can't possibly contain aRange.
+ if (_count < otherCount)
return NO;
- var i = 0,
- count = _ranges.length;
-
- // This is fast thanks to the _cachedIndexRange.
- for(; i < count; ++i)
- if (![anIndexSet containsIndexesInRange:_ranges[i]])
+ var otherRanges = anIndexSet._ranges,
+ otherRangesCount = otherRanges.length;
+
+ while (otherRangesCount--)
+ if (![self containsIndexesInRange:otherRanges[otherRangesCount]])
return NO;
-
+
return YES;
}
@@ -244,21 +227,20 @@
*/
- (BOOL)intersectsIndexesInRange:(CPRange)aRange
{
- // This is fast thanks to the _cachedIndexRange.
- if(!_count)
+ if (_count <= 0)
return NO;
-
- var i = SOERangeIndex(self, aRange.location),
- count = _ranges.length,
- upper = CPMaxRange(aRange);
-
- // Stop if the location is ever bigger than or equal to our
- // non-inclusive upper bound
- for (; i < count && _ranges[i].location < upper; ++i)
- if(CPIntersectionRange(aRange, _ranges[i]).length)
- return YES;
-
- return NO;
+
+ var lhsRangeIndex = assumedPositionOfIndex(_ranges, aRange.location);
+
+ if (FLOOR(lhsRangeIndex) === lhsRangeIndex)
+ return YES;
+
+ var rhsRangeIndex = assumedPositionOfIndex(_ranges, _CPMaxRange(aRange) - 1);
+
+ if (FLOOR(rhsRangeIndex) === rhsRangeIndex)
+ return YES;
+
+ return lhsRangeIndex !== rhsRangeIndex;
}
/*!
@@ -273,87 +255,103 @@
/*!
Return the first index in the set
*/
-- (int)firstIndex
+- (CPInteger)firstIndex
{
- return _count ? _ranges[0].location : CPNotFound;
+ if (_count > 0)
+ return _ranges[0].location;
+
+ return CPNotFound;
}
/*!
Returns the last index in the set
*/
-- (int)lastIndex
+- (CPInteger)lastIndex
{
- return _count ? CPMaxRange(_ranges[_ranges.length - 1]) - 1 : CPNotFound;
+ if (_count > 0)
+ return _CPMaxRange(_ranges[_ranges.length - 1]) - 1;
+
+ return CPNotFound;
}
/*!
Returns the first index value in the receiver which is greater than \c anIndex.
@return the closest index or CPNotFound if no match was found
*/
-- (unsigned)indexGreaterThanIndex:(unsigned)anIndex
+- (CPInteger)indexGreaterThanIndex:(CPInteger)anIndex
{
- if(!_count)
- return CPNotFound;
-
- var i = SOERangeIndex(self, anIndex++),
- count = _ranges.length;
-
- for(; i < count && anIndex >= CPMaxRange(_ranges[i]); ++i) ;
+ // The first possible index that would satisfy this requirement.
+ ++anIndex;
- if (i == count)
- return CPNotFound;
-
- _cachedRangeIndex = i;
-
- if (anIndex < _ranges[i].location)
- return _ranges[i].location;
+ // Attempt to find it or something bigger.
+ var rangeIndex = assumedPositionOfIndex(_ranges, anIndex);
- return anIndex;
+ // Nothing at all found?
+ if (rangeIndex === CPNotFound)
+ return CPNotFound;
+
+ rangeIndex = CEIL(rangeIndex);
+
+ if (rangeIndex >= _ranges.length)
+ return CPNotFound;
+
+ var range = _ranges[rangeIndex];
+
+ // Check if it's actually in this range.
+ if (CPLocationInRange(anIndex, range))
+ return anIndex;
+
+ // If not, it must be the first element of this range.
+ return range.location;
}
/*!
Returns the first index value in the receiver which is less than \c anIndex.
@return the closest index or CPNotFound if no match was found
*/
-- (unsigned)indexLessThanIndex:(unsigned)anIndex
+- (CPInteger)indexLessThanIndex:(CPInteger)anIndex
{
- if (!_count)
- return CPNotFound;
-
- var i = GOERangeIndex(self, anIndex--);
-
- for (; i >= 0 && anIndex < _ranges[i].location; --i) ;
+ // The first possible index that would satisfy this requirement.
+ --anIndex;
- if(i < 0)
- return CPNotFound;
-
- _cachedRangeIndex = i;
+ // Attempt to find it or something smaller.
+ var rangeIndex = assumedPositionOfIndex(_ranges, anIndex);
- if (CPLocationInRange(anIndex, _ranges[i]))
+ // Nothing at all found?
+ if (rangeIndex === CPNotFound)
+ return CPNotFound;
+
+ rangeIndex = FLOOR(rangeIndex);
+
+ if (rangeIndex < 0)
+ return CPNotFound;
+
+ var range = _ranges[rangeIndex];
+
+ // Check if it's actually in this range.
+ if (CPLocationInRange(anIndex, range))
return anIndex;
-
- if (CPMaxRange(_ranges[i]) - 1 < anIndex)
- return CPMaxRange(_ranges[i]) - 1;
- return CPNotFound;
+ // If not, it must be the first element of this range.
+ return _CPMaxRange(range) - 1;
}
/*!
Returns the first index value in the receiver which is greater than or equal to \c anIndex.
@return the matching index or CPNotFound if no match was found
*/
-- (unsigned int)indexGreaterThanOrEqualToIndex:(unsigned)anIndex
+- (CPInteger)indexGreaterThanOrEqualToIndex:(CPInteger)anIndex
{
- return [self indexGreaterThanIndex:anIndex - 1];
+ return [self indexGreaterThanIndex:anIndex - 1];
}
/*!
Returns the first index value in the receiver which is less than or equal to \c anIndex.
@return the matching index or CPNotFound if no match was found
*/
-- (unsigned int)indexLessThanOrEqualToIndex:(unsigned)anIndex
+- (CPInteger)indexLessThanOrEqualToIndex:(CPInteger)anIndex
{
- return [self indexLessThanIndex:anIndex + 1];
+ return [self indexLessThanIndex:anIndex + 1];
}
/*!
@@ -365,71 +363,105 @@
@param aRangePointer the range of indices to add
@return the number of elements added to the array
*/
-- (unsigned)getIndexes:(CPArray)anArray maxCount:(unsigned)aMaxCount inIndexRange:(CPRange)aRangePointer
+- (CPInteger)getIndexes:(CPArray)anArray maxCount:(CPInteger)aMaxCount inIndexRange:(CPRange)aRange
{
- if (!_count || aMaxCount <= 0 || aRangePointer && !aRangePointer.length)
- return 0;
-
- var i = SOERangeIndex(self, aRangePointer? aRangePointer.location : 0),
- total = 0,
- count = _ranges.length;
-
- for (; i < count; ++i)
+ if (!_count || aMaxCount === 0 || aRange && !aRange.length)
{
- // If aRangePointer is nil, all indexes are acceptable.
- var intersection = aRangePointer ? CPIntersectionRange(_ranges[i], aRangePointer) : _ranges[i],
- index = intersection.location,
- maximum = CPMaxRange(intersection);
-
- for (; index < maximum; ++index)
+ if (aRange)
+ aRange.length = 0;
+
+ return 0;
+ }
+
+ var total = 0;
+
+ if (aRange)
+ {
+ var firstIndex = aRange.location,
+ lastIndex = _CPMaxRange(aRange) - 1,
+ rangeIndex = CEIL(assumedPositionOfIndex(_ranges, firstIndex)),
+ lastRangeIndex = FLOOR(assumedPositionOfIndex(_ranges, lastIndex));
+ }
+ else
+ {
+ var firstIndex = [self firstIndex],
+ lastIndex = [self lastIndex],
+ rangeIndex = 0,
+ lastRangeIndex = _ranges.length - 1;
+ }
+
+ while (rangeIndex <= lastRangeIndex)
+ {
+ var range = _ranges[rangeIndex],
+ index = MAX(firstIndex, range.location),
+ maxRange = MIN(lastIndex + 1, _CPMaxRange(range));
+
+ for (; index < maxRange; ++index)
{
anArray[total++] = index;
-
- if (total == aMaxCount)
+
+ if (total === aMaxCount)
{
- // Update aRangePointer if it exists...
- if (aRangePointer)
+ // Update aRange if it exists...
+ if (aRange)
{
- var upper = CPMaxRange(aRangePointer);
-
- // Don't use CPMakeRange since the values need to persist.
- aRangePointer.location = index + 1;
- aRangePointer.length = upper - index - 1;
+ aRange.location = index + 1;
+ aRange.length = lastIndex + 1 - index - 1;
}
-
+
return aMaxCount;
}
}
+
+ ++rangeIndex;
}
-
- // Update aRangePointer if it exists...
- if (aRangePointer)
+
+ // Update aRange if it exists...
+ if (aRange)
{
- aRangePointer.location = CPNotFound;
- aRangePointer.length = 0;
+ aRange.location = CPNotFound;
+ aRange.length = 0;
}
-
+
return total;
}
- (CPString)description
{
- var desc = [super description] + " ";
-
- if (_count)
- {
- desc += "[number of indexes: " + _count + " (in " + _ranges.length + " ranges), indexes: (";
- for (i = 0; i < _ranges.length; i++)
- {
- desc += _ranges[i].location;
- if (_ranges[i].length > 1) desc += "-" + (CPMaxRange(_ranges[i])-1) + ":"+_ranges[i].length+":";
- if (i+1 < _ranges.length) desc += " ";
- }
- desc += ")]";
- }
- else
- desc += "(no indexes)";
- return desc;
+ var description = [super description];
+
+ if (_count)
+ {
+ var index = 0,
+ count = _ranges.length;
+
+ description += "[number of indexes: " + _count + " (in " + count;
+
+ if (count === 1)
+ description += " range), indexes: (";
+ else
+ description += " ranges), indexes: (";
+
+ for (; index < count; ++index)
+ {
+ var range = _ranges[index];
+
+ description += range.location;
+
+ if (range.length > 1)
+ description += "-" + (CPMaxRange(range) - 1);
+
+ if (index + 1 < count)
+ description += " ";
+ }
+
+ description += ")]";
+ }
+
+ else
+ description += "(no indexes)";
+
+ return description;
}
@end
@@ -441,9 +473,9 @@
Adds an index to the set.
@param anIndex the index to add
*/
-- (void)addIndex:(unsigned)anIndex
+- (void)addIndex:(CPInteger)anIndex
{
- [self addIndexesInRange:CPMakeRange(anIndex, 1)];
+ [self addIndexesInRange:_CPMakeRange(anIndex, 1)];
}
/*!
@@ -452,13 +484,12 @@
*/
- (void)addIndexes:(CPIndexSet)anIndexSet
{
- var i = 0,
- ranges = anIndexSet._ranges,
- count = ranges.length;
-
+ var otherRanges = anIndexSet._ranges,
+ otherRangesCount = otherRanges.length;
+
// Simply add each range within anIndexSet.
- for(; i < count; ++i)
- [self addIndexesInRange:ranges[i]];
+ while (otherRangesCount--)
+ [self addIndexesInRange:otherRanges[otherRangesCount]];
}
/*!
@@ -467,91 +498,66 @@
*/
- (void)addIndexesInRange:(CPRange)aRange
{
- if (_ranges.length == 0)
+ // If empty range, bail.
+ if (aRange.length <= 0)
+ return;
+
+ // If we currently don't have any indexes, this represents our entire set.
+ if (_count <= 0)
{
_count = aRange.length;
-
- return [_ranges addObject:CPCopyRange(aRange)];
- }
-
- // FIXME: Should we really use SOERangeIndex here? There is no real
- // reason the cached index would be a better guess than 0, and it
- // would avoid a function call.
- var i = SOERangeIndex(self, aRange.location),
- count = _ranges.length,
- padded = CPMakeRange(aRange.location - 1, aRange.length + 2),
- maximum = CPMaxRange(aRange);
+ _ranges = [aRange];
+
+ return;
+ }
+
+ var rangeCount = _ranges.length,
+ lhsRangeIndex = assumedPositionOfIndex(_ranges, aRange.location - 1),
+ lhsRangeIndexCEIL = CEIL(lhsRangeIndex);
+
+ if (lhsRangeIndexCEIL === lhsRangeIndex && lhsRangeIndexCEIL < rangeCount)
+ aRange = CPUnionRange(aRange, _ranges[lhsRangeIndexCEIL]);
+
+ var rhsRangeIndex = assumedPositionOfIndex(_ranges, CPMaxRange(aRange)),
+ rhsRangeIndexFLOOR = FLOOR(rhsRangeIndex);
+
+ if (rhsRangeIndexFLOOR === rhsRangeIndex && rhsRangeIndexFLOOR > 0)
+ aRange = CPUnionRange(aRange, _ranges[rhsRangeIndexFLOOR]);
+
+ var removalCount = rhsRangeIndexFLOOR - lhsRangeIndexCEIL + 1;
+
+ if (removalCount === _ranges.length)
+ {
+ _ranges = [aRange];
+ _count = aRange.length;
+ }
+
+ else if (removalCount === 1)
+ {
+ if (lhsRangeIndexCEIL < _ranges.length)
+ _count -= _ranges[lhsRangeIndexCEIL].length;
+
+ _count += aRange.length;
+ _ranges[lhsRangeIndexCEIL] = aRange;
+ }
- // If our range won't intersect with the last range, just append it to the end.
- if (count && CPMaxRange(_ranges[count - 1]) < aRange.location)
- [_ranges addObject:CPCopyRange(aRange)];
else
- for (; i < count; ++i)
+ {
+ if (removalCount > 0)
{
- // This range is completely independent of existing ranges,
- // simply add it to the array.
- if (maximum < _ranges[i].location)
- {
- _count += aRange.length;
-
- // Keep _cachedRangeIndex relevant.
- if (i < _cachedRangeIndex) ++_cachedRangeIndex;
-
- return [_ranges insertObject:CPCopyRange(aRange) atIndex:i];
- }
-
- if (CPIntersectionRange(_ranges[i], padded).length)
- {
- var union = CPUnionRange(_ranges[i], aRange);
-
- // We already contain all the indexes in this range.
- if (union.length == _ranges[i].length)
- return;
-
- // Pad the length to collapse with later ranges.
- ++union.length;
-
- // We only need to check if we now intersect with any following
- // ranges since if we now intersected with the previous range,
- // it would have already been handled. We start at i and not i + 1
- // to make sure we subtract i's length.
- var j = i;
-
- for(; j < count; ++j)
- // Bail as soon as we don't find an intersection.
- if(CPIntersectionRange(union, _ranges[j]).length)
- _count -= _ranges[j].length;
- else
- break;
-
- // Remove the padding now that we are done.
- // NOTE: We could have set _ranges[i] = CPCopyRange(union),
- // and then not had to bother decerementing here, but this avoids
- // a lookup above during unioning, and a function call (CPCopyRange).
- --union.length;
- _ranges[i] = union;
-
- // Now remove indexes [i + 1, j - 1]
- if (j - i - 1 > 0)
- {
- var remove = CPMakeRange(i + 1, j - i - 1);
-
- _ranges[i] = CPUnionRange(_ranges[i], _ranges[j - 1]);
- [_ranges removeObjectsInRange:remove];
-
- // Keep _cachedRangeIndex relevant.
- if (_cachedRangeIndex >= CPMaxRange(remove)) _cachedRangedIndex -= remove.length;
- else if (CPLocationInRange(_cachedRangeIndex, remove)) _cachedRangeIndex = i;
- }
-
- // Update count.
- _count += _ranges[i].length;
-
- return;
- }
+ var removal = lhsRangeIndexCEIL,
+ lastRemoval = lhsRangeIndexCEIL + removalCount - 1;
+
+ for (; removal <= lastRemoval; ++removal)
+ _count -= _ranges[removal].length;
+
+ [_ranges removeObjectsInRange:_CPMakeRange(lhsRangeIndexCEIL, removalCount)];
}
-
- _count += aRange.length;
+
+ [_ranges insertObject:aRange atIndex:lhsRangeIndexCEIL];
+
+ _count += aRange.length;
+ }
}
// Removing Indexes
@@ -559,9 +565,9 @@
Removes an index from the set
@param anIndex the index to remove
*/
-- (void)removeIndex:(unsigned int)anIndex
+- (void)removeIndex:(CPInteger)anIndex
{
- [self removeIndexesInRange:CPMakeRange(anIndex, 1)];
+ [self removeIndexesInRange:_CPMakeRange(anIndex, 1)];
}
/*!
@@ -571,13 +577,12 @@
*/
- (void)removeIndexes:(CPIndexSet)anIndexSet
{
- var i = 0,
- ranges = anIndexSet._ranges,
- count = ranges.length;
-
+ var otherRanges = anIndexSet._ranges,
+ otherRangesCount = otherRanges.length;
+
// Simply remove each index from anIndexSet
- for(; i < count; ++i)
- [self removeIndexesInRange:ranges[i]];
+ while (otherRangesCount--)
+ [self removeIndexesInRange:otherRanges[otherRangesCount]];
}
/*!
@@ -586,8 +591,7 @@
- (void)removeAllIndexes
{
_ranges = [];
- _count = 0;
- _cachedRangeIndex = 0;
+ _count = 0;
}
/*!
@@ -597,63 +601,78 @@
*/
- (void)removeIndexesInRange:(CPRange)aRange
{
- // FIXME: Should we really use SOERangeIndex here? There is no real
- // reason the cached index would be a better guess than 0, and it
- // would avoid a function call.
- var i = SOERangeIndex(self, aRange.location),
- count = _ranges.length,
- maximum = CPMaxRange(aRange),
- removal = CPMakeRange(CPNotFound, 0);
-
- for (; i < count; ++i)
- {
- var range = _ranges[i];
-
- // Our range will not intersect with any coming ranges.
- if (maximum < range.location)
- break;
+ // If empty range, bail.
+ if (aRange.length <= 0)
+ return;
- var intersection = CPIntersectionRange(range, aRange);
-
- // If we don't have an intersection, then just continue iterating.
- if (!intersection.length)
- continue;
-
- // If the intersection consists of the entirety of this range,
- // then remove it completely.
- else if (intersection.length == range.length)
+ // If we currently don't have any indexes, there's nothing to remove.
+ if (_count <= 0)
+ return;
+
+ var rangeCount = _ranges.length,
+ lhsRangeIndex = assumedPositionOfIndex(_ranges, aRange.location),
+ lhsRangeIndexCEIL = CEIL(lhsRangeIndex);
+
+ // Do we fall on an actual existing range?
+ if (lhsRangeIndex === lhsRangeIndexCEIL && lhsRangeIndexCEIL < rangeCount)
+ {
+ var existingRange = _ranges[lhsRangeIndexCEIL];
+
+ // If these ranges don't start in the same place, we have to cull it.
+ if (aRange.location !== existingRange.location)
{
- if (removal.location == CPNotFound)
- removal = CPMakeRange(i, 1);
+ var maxRange = CPMaxRange(aRange),
+ existingMaxRange = CPMaxRange(existingRange);
+
+ existingRange.length = aRange.location - existingRange.location;
+
+ // If this range is internal to the existing range, we have a unique splitting case.
+ if (maxRange < existingMaxRange)
+ {
+ _count -= aRange.length;
+ [_ranges insertObject:_CPMakeRange(maxRange, existingMaxRange - maxRange) atIndex:lhsRangeIndexCEIL + 1];
+
+ return;
+ }
else
- ++removal.length;
+ {
+ _count -= existingMaxRange - aRange.location;
+ lhsRangeIndexCEIL += 1;
+ }
}
- // If the intersection is contained entirely within this range,
- // then split it into two and return.
- else if (intersection.location > range.location && CPMaxRange(intersection) < CPMaxRange(range))
- {
- var insert = CPMakeRange(CPMaxRange(intersection), CPMaxRange(range) - CPMaxRange(intersection));
-
- range.length = intersection.location - range.location;
-
- _count -= intersection.length;
-
- return [_ranges insertObject:insert atIndex:i + 1];
- }
- // Else if we at least have an intersection, then trim the existing range.
- else
- {
- range.length -= intersection.length;
-
- if (intersection.location <= range.location)
- range.location += intersection.length;
- }
-
- _count -= intersection.length;
}
-
- if (removal.length)
- [_ranges removeObjectsInRange:removal];
+
+ var rhsRangeIndex = assumedPositionOfIndex(_ranges, CPMaxRange(aRange) - 1),
+ rhsRangeIndexFLOOR = FLOOR(rhsRangeIndex);
+
+ if (rhsRangeIndex === rhsRangeIndexFLOOR && rhsRangeIndexFLOOR >= 0)
+ {
+ var maxRange = CPMaxRange(aRange),
+ existingRange = _ranges[rhsRangeIndexFLOOR],
+ existingMaxRange = CPMaxRange(existingRange);
+
+ if (maxRange !== existingMaxRange)
+ {
+ _count -= maxRange - existingRange.location;
+ rhsRangeIndexFLOOR -= 1; // This is accounted for, and thus as if we got the previous spot.
+
+ existingRange.location = maxRange;
+ existingRange.length = existingMaxRange - maxRange;
+ }
+ }
+
+ var removalCount = rhsRangeIndexFLOOR - lhsRangeIndexCEIL + 1;
+
+ if (removalCount > 0)
+ {
+ var removal = lhsRangeIndexCEIL,
+ lastRemoval = lhsRangeIndexCEIL + removalCount - 1;
+
+ for (; removal <= lastRemoval; ++removal)
+ _count -= _ranges[removal].length;
+
+ [_ranges removeObjectsInRange:_CPMakeRange(lhsRangeIndexCEIL, removalCount)];
+ }
}
// Shifting Index Groups
@@ -663,11 +682,11 @@
@param aDelta the amount and direction to shift. A positive value shifts to
the right. A negative value shifts to the left.
*/
-- (void)shiftIndexesStartingAtIndex:(unsigned)anIndex by:(int)aDelta
-{
- if (!_count || aDelta == 0)
- return;
-
+- (void)shiftIndexesStartingAtIndex:(CPInteger)anIndex by:(int)aDelta
+{
+ if (!_count || aDelta == 0)
+ return;
+
// Later indexes have a higher probability of being shifted
// than lower ones, so start at the end and work backwards.
var i = _ranges.length - 1,
@@ -677,7 +696,7 @@
{
var range = _ranges[i],
maximum = CPMaxRange(range);
-
+
if (anIndex > maximum)
break;
@@ -699,11 +718,11 @@
shifted.length = CPMaxRange(shifted);
shifted.location = 0;
}
-
+
// We don't need to continue.
break;
}
-
+
// Shift the range, and normalize it if the result is negative.
if ((range.location += aDelta) < 0)
{
@@ -711,21 +730,21 @@
range.location = 0;
}
}
-
+
// We need to add the shifted ranges if the delta is negative.
if (aDelta < 0)
{
var j = i + 1,
count = _ranges.length,
shifts = [];
-
+
for (; j < count; ++j)
[shifts addObject:_ranges[j]];
-
+
if ((j = i + 1) < count)
{
[_ranges removeObjectsInRange:CPMakeRange(j, count - j)];
-
+
for (j = 0, count = shifts.length; j < count; ++j)
[self addIndexesInRange:shifts[j]];
}
@@ -738,7 +757,6 @@
@end
var CPIndexSetCountKey = @"CPIndexSetCountKey",
- CPIndexSetCachedRangeIndexKey = @"CPIndexSetCachedRangeIndexKey",
CPIndexSetRangeStringsKey = @"CPIndexSetRangeStringsKey";
@implementation CPIndexSet (CPCoding)
@@ -752,21 +770,20 @@ var CPIndexSetCountKey = @"CPIndexSetCountKey",
- (id)initWithCoder:(CPCoder)aCoder
{
self = [super init];
-
+
if (self)
{
_count = [aCoder decodeIntForKey:CPIndexSetCountKey];
- _cachedRangeIndex = [aCoder decodeIntForKey:CPIndexSetCachedRangeIndexKey];
_ranges = [];
-
+
var rangeStrings = [aCoder decodeObjectForKey:CPIndexSetRangeStringsKey],
index = 0,
count = rangeStrings.length;
-
+
for (; index < count; ++index)
_ranges.push(CPRangeFromString(rangeStrings[index]));
}
-
+
return self;
}
@@ -778,12 +795,11 @@ var CPIndexSetCountKey = @"CPIndexSetCountKey",
- (void)encodeWithCoder:(CPCoder)aCoder
{
[aCoder encodeInt:_count forKey:CPIndexSetCountKey];
- [aCoder encodeInt:_cachedRangeIndex forKey:CPIndexSetCachedRangeIndexKey];
-
+
var index = 0,
count = _ranges.length,
rangeStrings = [];
-
+
for (; index < count; ++index)
rangeStrings[index] = CPStringFromRange(_ranges[index]);
@@ -830,56 +846,102 @@ var CPIndexSetCountKey = @"CPIndexSetCountKey",
@end
-var SOERangeIndex = function(anIndexSet, anIndex)
+var positionOfIndex = function(ranges, anIndex)
{
- var ranges = anIndexSet._ranges,
- cachedRangeIndex = 0;//anIndexSet._cachedRangeIndex;
-
- if(cachedRangeIndex < ranges.length && anIndex >= ranges[cachedRangeIndex].location)
- return cachedRangeIndex;
+ var low = 0,
+ high = ranges.length - 1;
- return 0;
+ while (low <= high)
+ {
+ var middle = FLOOR(low + (high - low) / 2),
+ range = ranges[middle];
+
+ if (anIndex < range.location)
+ high = middle - 1;
+
+ else if (anIndex >= CPMaxRange(range))
+ low = middle + 1;
+
+ else
+ return middle;
+ }
+
+ return CPNotFound;
}
-var GOERangeIndex = function(anIndexSet, anIndex)
+var assumedPositionOfIndex = function(ranges, anIndex)
{
- var ranges = anIndexSet._ranges,
- cachedRangeIndex = anIndexSet._ranges.length;//anIndexSet._cachedRangeIndex;
-
- if(cachedRangeIndex < ranges.length && anIndex <= ranges[cachedRangeIndex].location)
- return cachedRangeIndex;
-
- return ranges.length - 1;
+ var count = ranges.length;
+
+ if (count <= 0)
+ return CPNotFound;
+
+ var low = 0,
+ high = count * 2;
+
+ while (low <= high)
+ {
+ var middle = FLOOR(low + (high - low) / 2),
+ position = middle / 2,
+ positionFLOOR = FLOOR(position);
+
+ if (position === positionFLOOR)
+ {
+ if (positionFLOOR - 1 >= 0 && anIndex < CPMaxRange(ranges[positionFLOOR - 1]))
+ high = middle - 1;
+
+ else if (positionFLOOR < count && anIndex >= ranges[positionFLOOR].location)
+ low = middle + 1;
+
+ else
+ return positionFLOOR - 0.5;
+ }
+ else
+ {
+ var range = ranges[positionFLOOR];
+
+ if (anIndex < range.location)
+ high = middle - 1;
+
+ else if (anIndex >= CPMaxRange(range))
+ low = middle + 1;
+
+ else
+ return positionFLOOR;
+ }
+ }
+
+ return CPNotFound;
}
/*
new old method
-X + (id)indexSet;
-X + (id)indexSetWithIndex:(unsigned int)value;
-X + (id)indexSetWithIndexesInRange:(NSRange)range;
-X X - (id)init;
-X X - (id)initWithIndex:(unsigned int)value;
-X X - (id)initWithIndexesInRange:(NSRange)range; // designated initializer
-X X - (id)initWithIndexSet:(NSIndexSet *)indexSet; // designated initializer
-X - (BOOL)isEqualToIndexSet:(NSIndexSet *)indexSet;
-X X - (unsigned int)count;
-X X - (unsigned int)firstIndex;
-X X - (unsigned int)lastIndex;
-X X - (unsigned int)indexGreaterThanIndex:(unsigned int)value;
-X X - (unsigned int)indexLessThanIndex:(unsigned int)value;
-X X - (unsigned int)indexGreaterThanOrEqualToIndex:(unsigned int)value;
-X X - (unsigned int)indexLessThanOrEqualToIndex:(unsigned int)value;
-X - (unsigned int)getIndexes:(unsigned int *)indexBuffer maxCount:(unsigned int)bufferSize inIndexRange:(NSRangePointer)range;
-X X - (BOOL)containsIndex:(unsigned int)value;
-X X - (BOOL)containsIndexesInRange:(NSRange)range;
-X X - (BOOL)containsIndexes:(NSIndexSet *)indexSet;
-X X - (BOOL)intersectsIndexesInRange:(NSRange)range;
-X X - (void)addIndexes:(NSIndexSet *)indexSet;
-X - (void)removeIndexes:(NSIndexSet *)indexSet;
-X X - (void)removeAllIndexes;
-X - (void)addIndex:(unsigned int)value;
-X - (void)removeIndex:(unsigned int)value;
-X - (void)addIndexesInRange:(NSRange)range;
-X - (void)removeIndexesInRange:(NSRange)range;
- - (void)shiftIndexesStartingAtIndex:(unsigned int)index by:(int)delta;
+X + (id)indexSet;
+X + (id)indexSetWithIndex:(unsigned int)value;
+X + (id)indexSetWithIndexesInRange:(NSRange)range;
+X X - (id)init;
+X X - (id)initWithIndex:(unsigned int)value;
+X X - (id)initWithIndexesInRange:(NSRange)range; // designated initializer
+X X - (id)initWithIndexSet:(NSIndexSet *)indexSet; // designated initializer
+X - (BOOL)isEqualToIndexSet:(NSIndexSet *)indexSet;
+X X - (unsigned int)count;
+X X - (unsigned int)firstIndex;
+X X - (unsigned int)lastIndex;
+X X - (unsigned int)indexGreaterThanIndex:(unsigned int)value;
+X X - (unsigned int)indexLessThanIndex:(unsigned int)value;
+X X - (unsigned int)indexGreaterThanOrEqualToIndex:(unsigned int)value;
+X X - (unsigned int)indexLessThanOrEqualToIndex:(unsigned int)value;
+X - (unsigned int)getIndexes:(unsigned int *)indexBuffer maxCount:(unsigned int)bufferSize inIndexRange:(NSRangePointer)range;
+X X - (BOOL)containsIndex:(unsigned int)value;
+X X - (BOOL)containsIndexesInRange:(NSRange)range;
+X X - (BOOL)containsIndexes:(NSIndexSet *)indexSet;
+X X - (BOOL)intersectsIndexesInRange:(NSRange)range;
+X X - (void)addIndexes:(NSIndexSet *)indexSet;
+X - (void)removeIndexes:(NSIndexSet *)indexSet;
+X X - (void)removeAllIndexes;
+X - (void)addIndex:(unsigned int)value;
+X - (void)removeIndex:(unsigned int)value;
+X - (void)addIndexesInRange:(NSRange)range;
+X - (void)removeIndexesInRange:(NSRange)range;
+ - (void)shiftIndexesStartingAtIndex:(unsigned int)index by:(int)delta;
*/
diff --git a/Foundation/CPKeyedUnarchiver.j b/Foundation/CPKeyedUnarchiver.j
index 05b737bb6..f9be40a40 100644
--- a/Foundation/CPKeyedUnarchiver.j
+++ b/Foundation/CPKeyedUnarchiver.j
@@ -108,7 +108,7 @@ var _CPKeyedUnarchiverArrayClass = Ni
CPDictionary _replacementClasses;
- CPDictionary _objects;
+ CPArray _objects;
CPDictionary _archive;
CPDictionary _plistObject;
diff --git a/TableCibTest/AppController.j b/TableCibTest/AppController.j
new file mode 100644
index 000000000..a079fb6e1
--- /dev/null
+++ b/TableCibTest/AppController.j
@@ -0,0 +1,45 @@
+/*
+ * AppController.j
+ * TableCibTest
+ *
+ * Created by Francisco Tolmasky on July 5, 2009.
+ * Copyright 2009, 280 North, Inc. All rights reserved.
+ */
+
+@import