#import "TUITableView.h"
#import "TUITableView+Cell.h"
#import "TUITableViewSectionHeader.h"
#import "TUINSView.h"
#define HEADER_Z_POSITION 1000
typedef struct {
CGFloat offset; CGFloat height;
} TUITableViewRowInfo;
@interface TUITableViewSection : NSObject
{
__unsafe_unretained TUITableView *_tableView; TUIView *_headerView; NSInteger sectionIndex;
NSUInteger numberOfRows;
CGFloat sectionHeight;
CGFloat sectionOffset;
TUITableViewRowInfo *rowInfo;
}
@property (strong, readonly) TUIView *headerView;
@property (nonatomic, assign) CGFloat sectionOffset;
@property (readonly) NSInteger sectionIndex;
@end
@implementation TUITableViewSection
@synthesize sectionOffset;
@synthesize sectionIndex;
- (id)initWithNumberOfRows:(NSUInteger)n sectionIndex:(NSInteger)s tableView:(TUITableView *)t
{
if((self = [super init])){
_tableView = t;
sectionIndex = s;
numberOfRows = n;
rowInfo = calloc(n, sizeof(TUITableViewRowInfo));
}
return self;
}
- (void)dealloc
{
if(rowInfo) free(rowInfo);
}
- (NSUInteger)numberOfRows
{
return numberOfRows;
}
- (void)_setupRowHeights
{
sectionHeight = 0.0;
TUIView *header;
if((header = self.headerView) != nil) {
sectionHeight += roundf(header.frame.size.height);
}
for(int i = 0; i < numberOfRows; ++i) {
CGFloat h = roundf([_tableView.delegate tableView:_tableView heightForRowAtIndexPath:[TUIFastIndexPath indexPathForRow:i inSection:sectionIndex]]);
rowInfo[i].offset = sectionHeight;
rowInfo[i].height = h;
sectionHeight += h;
}
}
- (CGFloat)rowHeight:(NSInteger)i
{
if(i >= 0 && i < numberOfRows) {
return rowInfo[i].height;
}
return 0.0;
}
- (CGFloat)sectionRowOffset:(NSInteger)i
{
if(i >= 0 && i < numberOfRows){
return rowInfo[i].offset;
}
return 0.0;
}
- (CGFloat)tableRowOffset:(NSInteger)i
{
return sectionOffset + [self sectionRowOffset:i];
}
- (CGFloat)sectionHeight
{
return sectionHeight;
}
- (CGFloat)headerHeight
{
return (self.headerView != nil) ? self.headerView.frame.size.height : 0;
}
- (TUIView *)headerView
{
if(_headerView == nil) {
if(_tableView.dataSource != nil && [_tableView.dataSource respondsToSelector:@selector(tableView:headerViewForSection:)]){
_headerView = [_tableView.dataSource tableView:_tableView headerViewForSection:sectionIndex];
_headerView.autoresizingMask = TUIViewAutoresizingFlexibleWidth;
_headerView.layer.zPosition = HEADER_Z_POSITION;
}
}
return _headerView;
}
@end
@interface TUITableView (Private)
- (void)_updateSectionInfo;
- (void)_updateDerepeaterViews;
@end
@implementation TUITableView
@synthesize pullDownView=_pullDownView;
@synthesize headerView=_headerView;
- (id)initWithFrame:(CGRect)frame style:(TUITableViewStyle)style
{
if((self = [super initWithFrame:frame])) {
_style = style;
_reusableTableCells = [[NSMutableDictionary alloc] init];
_visibleSectionHeaders = [[NSMutableIndexSet alloc] init];
_visibleItems = [[NSMutableDictionary alloc] init];
_tableFlags.animateSelectionChanges = 1;
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
return [self initWithFrame:frame style:TUITableViewStylePlain];
}
- (id<TUITableViewDelegate>)delegate
{
return (id<TUITableViewDelegate>)[super delegate];
}
- (void)setDelegate:(id<TUITableViewDelegate>)d
{
_tableFlags.delegateTableViewWillDisplayCellForRowAtIndexPath = [d respondsToSelector:@selector(tableView:willDisplayCell:forRowAtIndexPath:)];
[super setDelegate:d]; }
- (id<TUITableViewDataSource>)dataSource
{
return _dataSource;
}
- (void)setDataSource:(id<TUITableViewDataSource>)d
{
_dataSource = d;
_tableFlags.dataSourceNumberOfSectionsInTableView = [_dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)];
}
- (BOOL)animateSelectionChanges
{
return _tableFlags.animateSelectionChanges;
}
- (void)setAnimateSelectionChanges:(BOOL)a
{
_tableFlags.animateSelectionChanges = a;
}
- (NSInteger)numberOfSections
{
return [_sectionInfo count];
}
- (NSInteger)numberOfRowsInSection:(NSInteger)section
{
return [[_sectionInfo objectAtIndex:section] numberOfRows];
}
- (CGRect)rectForHeaderOfSection:(NSInteger)section {
if(section >= 0 && section < [_sectionInfo count]){
TUITableViewSection *s = [_sectionInfo objectAtIndex:section];
CGFloat offset = [s sectionOffset];
CGFloat height = [s headerHeight];
CGFloat y = _contentHeight - offset - height;
return CGRectMake(0, y, self.bounds.size.width, height);
}
return CGRectZero;
}
- (CGRect)rectForSection:(NSInteger)section
{
if(section >= 0 && section < [_sectionInfo count]){
TUITableViewSection *s = [_sectionInfo objectAtIndex:section];
CGFloat offset = [s sectionOffset];
CGFloat height = [s sectionHeight];
CGFloat y = _contentHeight - offset - height;
return CGRectMake(0, y, self.bounds.size.width, height);
}
return CGRectZero;
}
- (CGRect)rectForRowAtIndexPath:(TUIFastIndexPath *)indexPath
{
NSInteger section = indexPath.section;
NSInteger row = indexPath.row;
if(section >= 0 && section < [_sectionInfo count]) {
TUITableViewSection *s = [_sectionInfo objectAtIndex:section];
CGFloat offset = [s tableRowOffset:row];
CGFloat height = [s rowHeight:row];
CGFloat y = _contentHeight - offset - height;
return CGRectMake(0, y, self.bounds.size.width, height);
}
return CGRectZero;
}
- (void)_updateSectionInfo {
if(_sectionInfo != nil){
for(TUITableViewSection *section in _sectionInfo){
TUIView *headerView;
if((headerView = [section headerView]) != nil){
[headerView removeFromSuperview];
}
}
[_visibleSectionHeaders removeAllIndexes];
_sectionInfo = nil;
}
NSInteger numberOfSections = 1;
if(_tableFlags.dataSourceNumberOfSectionsInTableView){
numberOfSections = [_dataSource numberOfSectionsInTableView:self];
}
NSMutableArray *sections = [[NSMutableArray alloc] initWithCapacity:numberOfSections];
CGFloat offset = [_headerView bounds].size.height - self.contentInset.top*2;
for(int s = 0; s < numberOfSections; ++s) {
TUITableViewSection *section = [[TUITableViewSection alloc] initWithNumberOfRows:[_dataSource tableView:self numberOfRowsInSection:s] sectionIndex:s tableView:self];
[section _setupRowHeights];
section.sectionOffset = offset;
offset += [section sectionHeight];
[sections addObject:section];
}
_contentHeight = offset - self.contentInset.bottom;
_sectionInfo = sections;
}
- (void)_enqueueReusableCell:(TUITableViewCell *)cell
{
NSString *identifier = cell.reuseIdentifier;
if(!identifier)
return;
NSMutableArray *array = [_reusableTableCells objectForKey:identifier];
if(!array) {
array = [[NSMutableArray alloc] init];
[_reusableTableCells setObject:array forKey:identifier];
}
[array addObject:cell];
}
- (TUITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier
{
if(!identifier)
return nil;
NSMutableArray *array = [_reusableTableCells objectForKey:identifier];
if(array) {
TUITableViewCell *c = [array lastObject];
if(c) {
[array removeLastObject];
[c prepareForReuse];
return c;
}
}
return nil;
}
- (TUIView *)headerViewForSection:(NSInteger)section {
if(section >= 0 && section < [_sectionInfo count]){
return [(TUITableViewSection *)[_sectionInfo objectAtIndex:section] headerView];
}else{
return nil;
}
}
- (TUITableViewCell *)cellForRowAtIndexPath:(TUIFastIndexPath *)indexPath {
return [_visibleItems objectForKey:indexPath];
}
- (NSArray *)visibleCells
{
return [_visibleItems allValues];
}
static NSInteger SortCells(TUITableViewCell *a, TUITableViewCell *b, void *ctx)
{
if(a.frame.origin.y > b.frame.origin.y)
return NSOrderedAscending;
return NSOrderedDescending;
}
- (NSArray *)sortedVisibleCells
{
NSArray *v = [self visibleCells];
return [v sortedArrayUsingComparator:(NSComparator)^NSComparisonResult(TUITableViewCell *a, TUITableViewCell *b) {
if(a.frame.origin.y > b.frame.origin.y)
return NSOrderedAscending;
return NSOrderedDescending;
}];
}
#define INDEX_PATHS_FOR_VISIBLE_ROWS [_visibleItems allKeys]
- (NSArray *)indexPathsForVisibleRows
{
return INDEX_PATHS_FOR_VISIBLE_ROWS;
}
- (TUIFastIndexPath *)indexPathForCell:(TUITableViewCell *)c
{
for(TUIFastIndexPath *i in _visibleItems) {
TUITableViewCell *cell = [_visibleItems objectForKey:i];
if(cell == c)
return i;
}
return nil;
}
- (NSIndexSet *)indexesOfSectionsInRect:(CGRect)rect
{
NSMutableIndexSet *indexes = [[NSMutableIndexSet alloc] init];
for(int i = 0; i < [_sectionInfo count]; i++) {
if(CGRectIntersectsRect([self rectForSection:i], rect)){
[indexes addIndex:i];
}
}
return indexes;
}
- (NSIndexSet *)indexesOfSectionHeadersInRect:(CGRect)rect
{
NSMutableIndexSet *indexes = [[NSMutableIndexSet alloc] init];
for(int i = 0; i < [_sectionInfo count]; i++) {
if(CGRectIntersectsRect([self rectForHeaderOfSection:i], rect)){
[indexes addIndex:i];
}
}
return indexes;
}
- (NSArray *)indexPathsForRowsInRect:(CGRect)rect
{
NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:50];
NSInteger sectionIndex = 0;
for(TUITableViewSection *section in _sectionInfo) {
NSInteger numberOfRows = [section numberOfRows];
for(NSInteger row = 0; row < numberOfRows; ++row) {
TUIFastIndexPath *indexPath = [TUIFastIndexPath indexPathForRow:row inSection:sectionIndex];
CGRect cellRect = [self rectForRowAtIndexPath:indexPath];
if(CGRectIntersectsRect(cellRect, rect)) {
[indexPaths addObject:indexPath];
} else {
}
}
++sectionIndex;
}
return indexPaths;
}
- (TUIFastIndexPath *)indexPathForRowAtPoint:(CGPoint)point {
NSInteger sectionIndex = 0;
for(TUITableViewSection *section in _sectionInfo){
for(NSInteger row = 0; row < [section numberOfRows]; row++){
TUIFastIndexPath *indexPath = [TUIFastIndexPath indexPathForRow:row inSection:sectionIndex];
CGRect cellRect = [self rectForRowAtIndexPath:indexPath];
if(CGRectContainsPoint(cellRect, point)){
return indexPath;
}
}
++sectionIndex;
}
return nil;
}
- (TUIFastIndexPath *)indexPathForRowAtVerticalOffset:(CGFloat)offset {
NSInteger sectionIndex = 0;
for(TUITableViewSection *section in _sectionInfo){
for(NSInteger row = 0; row < [section numberOfRows]; row++){
TUIFastIndexPath *indexPath = [TUIFastIndexPath indexPathForRow:row inSection:sectionIndex];
CGRect cellRect = [self rectForRowAtIndexPath:indexPath];
if(offset >= cellRect.origin.y && offset <= (cellRect.origin.y + cellRect.size.height)){
return indexPath;
}
}
++sectionIndex;
}
return nil;
}
- (NSInteger)indexOfSectionWithHeaderAtPoint:(CGPoint)point {
NSInteger sectionIndex = 0;
for(TUITableViewSection *section in _sectionInfo){
TUIView *headerView;
if((headerView = section.headerView) != nil){
CGFloat offset = [section sectionOffset];
CGFloat height = [section headerHeight];
CGFloat y = _contentHeight - offset - height;
CGRect frame = CGRectMake(0, y, self.bounds.size.width, height);
if(point.y > frame.origin.y && point.y < (frame.origin.y + frame.size.height)){
return sectionIndex;
}
}
sectionIndex++;
}
return -1;
}
- (NSInteger)indexOfSectionWithHeaderAtVerticalOffset:(CGFloat)offset {
NSInteger sectionIndex = 0;
for(TUITableViewSection *section in _sectionInfo){
TUIView *headerView;
if((headerView = section.headerView) != nil){
CGFloat offset = [section sectionOffset];
CGFloat height = [section headerHeight];
CGFloat y = _contentHeight - offset - height;
CGRect frame = CGRectMake(0, y, self.bounds.size.width, height);
if(offset >= frame.origin.y && offset <= (frame.origin.y + frame.size.height)){
return sectionIndex;
}
}
sectionIndex++;
}
return -1;
}
- (void)enumerateIndexPathsUsingBlock:(void (^)(TUIFastIndexPath *indexPath, BOOL *stop))block {
[self enumerateIndexPathsFromIndexPath:nil toIndexPath:nil withOptions:0 usingBlock:block];
}
- (void)enumerateIndexPathsWithOptions:(NSEnumerationOptions)options usingBlock:(void (^)(TUIFastIndexPath *indexPath, BOOL *stop))block {
[self enumerateIndexPathsFromIndexPath:nil toIndexPath:nil withOptions:options usingBlock:block];
}
- (void)enumerateIndexPathsFromIndexPath:(TUIFastIndexPath *)fromIndexPath toIndexPath:(TUIFastIndexPath *)toIndexPath withOptions:(NSEnumerationOptions)options usingBlock:(void (^)(TUIFastIndexPath *indexPath, BOOL *stop))block {
NSInteger sectionLowerBound = (fromIndexPath != nil) ? fromIndexPath.section : 0;
NSInteger sectionUpperBound = (toIndexPath != nil) ? toIndexPath.section : [self numberOfSections] - 1;
NSInteger rowLowerBound = (fromIndexPath != nil) ? fromIndexPath.row : 0;
NSInteger rowUpperBound = (toIndexPath != nil) ? toIndexPath.row : -1;
NSInteger irow = rowLowerBound; for(NSInteger i = sectionLowerBound; i < [self numberOfSections] && i <= sectionUpperBound ; i++){
NSInteger rowCount = [self numberOfRowsInSection:i];
for(NSInteger j = irow; j < rowCount && j <= ((rowUpperBound < 0 || i < sectionUpperBound) ? rowCount - 1 : rowUpperBound) ; j++){
BOOL stop = FALSE;
block([TUIFastIndexPath indexPathForRow:j inSection:i], &stop);
if(stop) return;
}
irow = 0; }
}
- (TUIFastIndexPath *)_topVisibleIndexPath
{
TUIFastIndexPath *topVisibleIndex = nil;
NSArray *v = [INDEX_PATHS_FOR_VISIBLE_ROWS sortedArrayUsingSelector:@selector(compare:)];
if([v count])
topVisibleIndex = [v objectAtIndex:0];
return topVisibleIndex;
}
- (void)setFrame:(CGRect)f
{
_tableFlags.forceSaveScrollPosition = 1;
[super setFrame:f];
}
- (void)setContentOffset:(CGPoint)p
{
_tableFlags.didFirstLayout = 1; [super setContentOffset:p];
if([self __isDraggingCell]){
[self __updateDraggingCell:_dragToReorderCell offset:_currentDragToReorderMouseOffset location:_currentDragToReorderLocation];
}
}
- (void)setPullDownView:(TUIView *)p
{
[_pullDownView removeFromSuperview];
_pullDownView = p;
[self addSubview:_pullDownView];
_pullDownView.hidden = YES;
}
- (void)setHeaderView:(TUIView *)h
{
[_headerView removeFromSuperview];
_headerView = h;
[self addSubview:_headerView];
_headerView.hidden = YES;
}
- (BOOL)_preLayoutCells
{
CGRect bounds = self.bounds;
if(!_sectionInfo || !CGSizeEqualToSize(bounds.size, _lastSize)) {
CGFloat previousOffset = 0.0f;
TUIFastIndexPath *savedIndexPath = nil;
CGFloat relativeOffset = 0.0;
if(_tableFlags.maintainContentOffsetAfterReload) {
previousOffset = self.contentSize.height + self.contentOffset.y;
} else {
if(_tableFlags.forceSaveScrollPosition || [self.nsView inLiveResize]) {
_tableFlags.forceSaveScrollPosition = 0;
NSArray *a = [INDEX_PATHS_FOR_VISIBLE_ROWS sortedArrayUsingSelector:@selector(compare:)];
if([a count]) {
savedIndexPath = [a objectAtIndex:0];
CGRect v = [self visibleRect];
CGRect r = [self rectForRowAtIndexPath:savedIndexPath];
relativeOffset = ((v.origin.y + v.size.height) - (r.origin.y + r.size.height));
relativeOffset += (_lastSize.height - bounds.size.height);
}
} else if(_keepVisibleIndexPathForReload) {
savedIndexPath = _keepVisibleIndexPathForReload;
relativeOffset = _relativeOffsetForReload;
_keepVisibleIndexPathForReload = nil;
}
}
[self _updateSectionInfo]; self.contentSize = CGSizeMake(self.bounds.size.width, _contentHeight);
_lastSize = bounds.size;
if(!_tableFlags.didFirstLayout) {
_tableFlags.didFirstLayout = 1;
[self scrollToTopAnimated:NO];
}
if(_tableFlags.maintainContentOffsetAfterReload) {
CGFloat newOffset = previousOffset - self.contentSize.height;
self.contentOffset = CGPointMake(self.contentOffset.x, newOffset);
} else {
if(savedIndexPath) {
CGRect v = [self visibleRect];
CGRect r = [self rectForRowAtIndexPath:savedIndexPath];
r.origin.y -= (v.size.height - r.size.height);
r.size.height += (v.size.height - r.size.height);
r.origin.y += relativeOffset;
[self scrollRectToVisible:r animated:NO];
}
}
return YES; }
return NO; }
- (void)_layoutSectionHeaders:(BOOL)visibleHeadersNeedRelayout
{
CGRect visible = [self visibleRect];
NSIndexSet *oldIndexes = _visibleSectionHeaders;
NSIndexSet *newIndexes = [self indexesOfSectionsInRect:visible];
NSMutableIndexSet *toRemove = [oldIndexes mutableCopy];
[toRemove removeIndexes:newIndexes];
NSMutableIndexSet *toAdd = [newIndexes mutableCopy];
[toAdd removeIndexes:oldIndexes];
__block TUIView *pinnedHeader = nil;
[newIndexes enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) {
if(index < [_sectionInfo count]) {
TUITableViewSection *section = [_sectionInfo objectAtIndex:index];
if(section.headerView != nil) {
CGRect headerFrame = [self rectForHeaderOfSection:index];
if(_style == TUITableViewStyleGrouped) {
if(CGRectGetMaxY(headerFrame) > CGRectGetMaxY(visible)) {
headerFrame.origin.y = CGRectGetMaxY(visible) - headerFrame.size.height;
pinnedHeader = section.headerView;
if([section.headerView isKindOfClass:[TUITableViewSectionHeader class]]){
((TUITableViewSectionHeader *)section.headerView).pinnedToViewport = TRUE;
}
}else if((pinnedHeader != nil) && (CGRectGetMaxY(headerFrame) > pinnedHeader.frame.origin.y)) {
CGRect pinnedHeaderFrame = pinnedHeader.frame;
pinnedHeaderFrame.origin.y = CGRectGetMaxY(headerFrame);
pinnedHeader.frame = pinnedHeaderFrame;
if([section.headerView isKindOfClass:[TUITableViewSectionHeader class]]){
((TUITableViewSectionHeader *)section.headerView).pinnedToViewport = FALSE;
}
}else{
if([section.headerView isKindOfClass:[TUITableViewSectionHeader class]]){
((TUITableViewSectionHeader *)section.headerView).pinnedToViewport = FALSE;
}
}
}
section.headerView.frame = headerFrame;
[section.headerView setNeedsLayout];
if(section.headerView.superview == nil){
[self addSubview:section.headerView];
}
}
}
[_visibleSectionHeaders addIndex:index];
}];
[toRemove enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) {
if(index < [_sectionInfo count]) {
TUITableViewSection *section = [_sectionInfo objectAtIndex:index];
if(section.headerView != nil) {
[section.headerView removeFromSuperview];
}
}
[_visibleSectionHeaders removeIndex:index];
}];
}
- (void)_layoutCells:(BOOL)visibleCellsNeedRelayout
{
if(visibleCellsNeedRelayout) {
for(TUIFastIndexPath *i in _visibleItems) {
TUITableViewCell *cell = [_visibleItems objectForKey:i];
cell.frame = [self rectForRowAtIndexPath:i];
cell.layer.zPosition = 0;
[cell setNeedsLayout];
}
}
CGRect visible = [self visibleRect];
NSArray *oldVisibleIndexPaths = INDEX_PATHS_FOR_VISIBLE_ROWS;
NSArray *newVisibleIndexPaths = [self indexPathsForRowsInRect:visible];
NSMutableArray *indexPathsToRemove = [oldVisibleIndexPaths mutableCopy];
[indexPathsToRemove removeObjectsInArray:newVisibleIndexPaths];
NSMutableArray *indexPathsToAdd = [newVisibleIndexPaths mutableCopy];
[indexPathsToAdd removeObjectsInArray:oldVisibleIndexPaths];
for(TUIFastIndexPath *i in indexPathsToRemove) {
TUITableViewCell *cell = [self cellForRowAtIndexPath:i];
if(_dragToReorderCell == nil || ![cell isEqual:_dragToReorderCell]){
[self _enqueueReusableCell:cell];
[cell removeFromSuperview];
[_visibleItems removeObjectForKey:i];
}
}
for(TUIFastIndexPath *i in indexPathsToAdd) {
if([_visibleItems objectForKey:i]) {
NSLog(@"!!! Warning: already have a cell in place for index path %@\n\n\n", i);
} else {
TUITableViewCell *cell = [_dataSource tableView:self cellForRowAtIndexPath:i];
[self.nsView invalidateHoverForView:cell];
cell.frame = [self rectForRowAtIndexPath:i];
cell.layer.zPosition = 0;
[cell setNeedsLayout];
[cell prepareForDisplay];
if([i isEqual:_selectedIndexPath]) {
[cell setSelected:YES animated:NO];
} else {
[cell setSelected:NO animated:NO];
}
if(_tableFlags.delegateTableViewWillDisplayCellForRowAtIndexPath) {
[_delegate tableView:self willDisplayCell:cell forRowAtIndexPath:i];
}
[self addSubview:cell];
if([_indexPathShouldBeFirstResponder isEqual:i]) {
if([cell acceptsFirstResponder]){
[self.nsWindow makeFirstResponderIfNotAlreadyInResponderChain:cell withFutureRequestToken:_futureMakeFirstResponderToken];
}
_indexPathShouldBeFirstResponder = nil;
}
[_visibleItems setObject:cell forKey:i];
}
}
if([indexPathsToAdd count] > 0 && _dragToReorderCell != nil){
[[_dragToReorderCell superview] bringSubviewToFront:_dragToReorderCell];
}
if(_headerView) {
CGSize s = self.contentSize;
CGRect headerViewRect = CGRectMake(0, s.height - _headerView.frame.size.height, visible.size.width, _headerView.frame.size.height);
if(CGRectIntersectsRect(headerViewRect, visible)) {
_headerView.frame = headerViewRect;
[_headerView setNeedsLayout];
if(_headerView.hidden) {
_headerView.hidden = NO;
}
} else {
if(!_headerView.hidden) {
_headerView.hidden = YES;
}
}
}
if(_pullDownView) {
CGSize s = self.contentSize;
CGRect pullDownRect = CGRectMake(0, s.height, visible.size.width, _pullDownView.frame.size.height);
if([self pullDownViewIsVisible]) {
if(_pullDownView.hidden) {
_pullDownView.frame = pullDownRect;
_pullDownView.hidden = NO;
}
} else {
if(!_pullDownView.hidden) {
_pullDownView.hidden = YES;
}
}
}
}
- (BOOL)pullDownViewIsVisible
{
if(_pullDownView) {
CGSize s = self.contentSize;
CGRect visible = [self visibleRect];
CGRect pullDownRect = CGRectMake(0, s.height, visible.size.width, _pullDownView.frame.size.height);
return CGRectIntersectsRect(pullDownRect, visible);
}
return NO;
}
- (void)reloadDataMaintainingVisibleIndexPath:(TUIFastIndexPath *)indexPath relativeOffset:(CGFloat)relativeOffset
{
_keepVisibleIndexPathForReload = indexPath;
_relativeOffsetForReload = relativeOffset;
[self reloadData];
}
- (void)reloadData
{
if(self.delegate != nil && [self.delegate respondsToSelector:@selector(tableViewWillReloadData:)]){
[self.delegate tableViewWillReloadData:self];
}
_selectedIndexPath = nil;
for(TUIFastIndexPath *i in _visibleItems) {
TUITableViewCell *cell = [_visibleItems objectForKey:i];
[self _enqueueReusableCell:cell];
[cell removeFromSuperview];
}
_dragToReorderCell = nil;
[_visibleItems removeAllObjects];
for(TUITableViewSection *section in _sectionInfo){
TUIView *headerView;
if((headerView = [section headerView]) != nil){
[headerView removeFromSuperview];
}
}
[_visibleSectionHeaders removeAllIndexes];
_sectionInfo = nil;
[self layoutSubviews];
if(self.delegate != nil && [self.delegate respondsToSelector:@selector(tableViewDidReloadData:)]){
[self.delegate tableViewDidReloadData:self];
}
}
- (void)layoutSubviews
{
if(!_tableFlags.layoutSubviewsReentrancyGuard) {
_tableFlags.layoutSubviewsReentrancyGuard = 1;
[TUIView setAnimationsEnabled:NO block:^{
[CATransaction begin];
[CATransaction setDisableActions:YES];
BOOL visibleCellsNeedRelayout = [self _preLayoutCells];
[super layoutSubviews]; [self _layoutSectionHeaders:visibleCellsNeedRelayout];
[self _layoutCells:visibleCellsNeedRelayout];
if(_tableFlags.derepeaterEnabled)
[self _updateDerepeaterViews];
[CATransaction commit];
}];
_tableFlags.layoutSubviewsReentrancyGuard = 0;
} else {
}
}
- (void)reloadLayout
{
_sectionInfo = nil;
[self _preLayoutCells];
[super layoutSubviews]; [self _layoutSectionHeaders:YES];
[self _layoutCells:YES];
}
- (void)scrollToRowAtIndexPath:(TUIFastIndexPath *)indexPath atScrollPosition:(TUITableViewScrollPosition)scrollPosition animated:(BOOL)animated
{
CGRect v = [self visibleRect];
CGRect r = [self rectForRowAtIndexPath:indexPath];
TUIView *headerView;
if((headerView = [self headerViewForSection:indexPath.section]) != nil){
CGRect headerFrame = [self rectForHeaderOfSection:indexPath.section];
r.size.height += headerFrame.size.height;
}
switch(scrollPosition) {
case TUITableViewScrollPositionNone:
break;
case TUITableViewScrollPositionTop:
r.origin.y -= (v.size.height - r.size.height);
r.size.height += (v.size.height - r.size.height);
[self scrollRectToVisible:r animated:animated];
break;
case TUITableViewScrollPositionToVisible:
default:
[self scrollRectToVisible:r animated:animated];
break;
}
}
- (TUIFastIndexPath *)indexPathForSelectedRow
{
return _selectedIndexPath;
}
- (TUIFastIndexPath *)indexPathForFirstRow
{
return [TUIFastIndexPath indexPathForRow:0 inSection:0];
}
- (TUIFastIndexPath *)indexPathForLastRow
{
NSInteger sec = [self numberOfSections] - 1;
NSInteger row = [self numberOfRowsInSection:sec] - 1;
return [TUIFastIndexPath indexPathForRow:row inSection:sec];
}
- (void)_makeRowAtIndexPathFirstResponder:(TUIFastIndexPath *)indexPath
{
TUITableViewCell *cell = [self cellForRowAtIndexPath:indexPath];
if(cell && [cell acceptsFirstResponder]) {
[self.nsWindow makeFirstResponderIfNotAlreadyInResponderChain:cell];
} else {
_indexPathShouldBeFirstResponder = indexPath;
_futureMakeFirstResponderToken = [self.nsWindow futureMakeFirstResponderRequestToken];
}
}
- (void)selectRowAtIndexPath:(TUIFastIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(TUITableViewScrollPosition)scrollPosition
{
TUIFastIndexPath *oldIndexPath = [self indexPathForSelectedRow];
[self deselectRowAtIndexPath:[self indexPathForSelectedRow] animated:animated];
TUITableViewCell *cell = [self cellForRowAtIndexPath:indexPath]; [cell setSelected:YES animated:animated];
_selectedIndexPath = indexPath;
[cell setNeedsDisplay];
if([self.delegate respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]){
[self.delegate tableView:self didSelectRowAtIndexPath:indexPath];
}
NSResponder *firstResponder = [self.nsWindow firstResponder];
if(firstResponder == self || firstResponder == [self cellForRowAtIndexPath:oldIndexPath]) {
[self _makeRowAtIndexPathFirstResponder:indexPath];
}
[self scrollToRowAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated];
}
- (void)deselectRowAtIndexPath:(TUIFastIndexPath *)indexPath animated:(BOOL)animated
{
if([indexPath isEqual:_selectedIndexPath]) {
TUITableViewCell *cell = [self cellForRowAtIndexPath:indexPath];
[cell setSelected:NO animated:animated];
_selectedIndexPath = nil;
[cell setNeedsDisplay];
if([self.delegate respondsToSelector:@selector(tableView:didDeselectRowAtIndexPath:)]){
[self.delegate tableView:self didDeselectRowAtIndexPath:indexPath];
}
}
}
- (TUIFastIndexPath *)indexPathForFirstVisibleRow
{
TUIFastIndexPath *firstIndexPath = nil;
for(TUIFastIndexPath *indexPath in _visibleItems) {
if(firstIndexPath == nil || [indexPath compare:firstIndexPath] == NSOrderedAscending) {
firstIndexPath = indexPath;
}
}
return firstIndexPath;
}
- (TUIFastIndexPath *)indexPathForLastVisibleRow
{
TUIFastIndexPath *lastIndexPath = nil;
for(TUIFastIndexPath *indexPath in _visibleItems) {
if(lastIndexPath == nil || [indexPath compare:lastIndexPath] == NSOrderedDescending) {
lastIndexPath = indexPath;
}
}
return lastIndexPath;
}
- (BOOL)performKeyAction:(NSEvent *)event
{
BOOL noCurrentSelection = (_selectedIndexPath == nil || ([self cellForRowAtIndexPath:_selectedIndexPath] == nil && ![event isARepeat]));;
typedef TUIFastIndexPath * (^TUITableViewCalculateNextIndexPathBlock)(TUIFastIndexPath *lastIndexPath);
void (^selectValidIndexPath)(TUIFastIndexPath *startForNoSelection, TUITableViewCalculateNextIndexPathBlock calculateNextIndexPath) = ^(TUIFastIndexPath *startForNoSelection, TUITableViewCalculateNextIndexPathBlock calculateNextIndexPath) {
NSParameterAssert(calculateNextIndexPath != nil);
BOOL foundValidNextRow = NO;
TUIFastIndexPath *lastIndexPath = _selectedIndexPath;
while(!foundValidNextRow) {
TUIFastIndexPath *newIndexPath;
if(noCurrentSelection && lastIndexPath == nil) {
newIndexPath = startForNoSelection;
} else {
newIndexPath = calculateNextIndexPath(lastIndexPath);
}
if(![_delegate respondsToSelector:@selector(tableView:shouldSelectRowAtIndexPath:forEvent:)] || [_delegate tableView:self shouldSelectRowAtIndexPath:newIndexPath forEvent:event]){
[self selectRowAtIndexPath:newIndexPath animated:self.animateSelectionChanges scrollPosition:TUITableViewScrollPositionToVisible];
foundValidNextRow = YES;
}
if([lastIndexPath isEqual:newIndexPath]) foundValidNextRow = YES;
lastIndexPath = newIndexPath;
}
};
switch([[event charactersIgnoringModifiers] characterAtIndex:0]) {
case NSUpArrowFunctionKey: {
selectValidIndexPath([self indexPathForLastVisibleRow], ^(TUIFastIndexPath *lastIndexPath) {
NSUInteger section = lastIndexPath.section;
NSUInteger row = lastIndexPath.row;
if(row > 0) {
row--;
} else {
while(section > 0) {
section--;
NSUInteger rowsInSection = [self numberOfRowsInSection:section];
if(rowsInSection > 0) {
row = rowsInSection - 1;
break;
}
}
}
return [TUIFastIndexPath indexPathForRow:row inSection:section];
});
return YES;
}
case NSDownArrowFunctionKey: {
selectValidIndexPath([self indexPathForFirstVisibleRow], ^(TUIFastIndexPath *lastIndexPath) {
NSUInteger section = lastIndexPath.section;
NSUInteger row = lastIndexPath.row;
NSUInteger rowsInSection = [self numberOfRowsInSection:section];
if(row + 1 < rowsInSection) {
row++;
} else {
NSUInteger sections = [self numberOfSections];
while(section + 1 < sections) {
section++;
NSUInteger rowsInSection = [self numberOfRowsInSection:section];
if(rowsInSection > 0) {
row = 0;
break;
}
}
}
return [TUIFastIndexPath indexPathForRow:row inSection:section];
});
return YES;
}
}
return [super performKeyAction:event];
}
- (BOOL)maintainContentOffsetAfterReload
{
return _tableFlags.maintainContentOffsetAfterReload;
}
- (void)setMaintainContentOffsetAfterReload:(BOOL)newValue
{
_tableFlags.maintainContentOffsetAfterReload = newValue;
}
@end
@implementation NSIndexPath (TUITableView)
+ (NSIndexPath *)indexPathForRow:(NSUInteger)row inSection:(NSUInteger)section
{
NSUInteger i[] = {section, row};
return [NSIndexPath indexPathWithIndexes:i length:2];
}
- (NSUInteger)section
{
return [self indexAtPosition:0];
}
- (NSUInteger)row
{
return [self indexAtPosition:1];
}
@end