mirror of
				https://github.com/KevinMidboe/linguist.git
				synced 2025-10-29 17:50:22 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1249 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
			
		
		
	
	
			1249 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
| /*
 | |
|  Copyright 2011 Twitter, Inc.
 | |
|  
 | |
|  Licensed under the Apache License, Version 2.0 (the "License");
 | |
|  you may not use this work except in compliance with the License.
 | |
|  You may obtain a copy of the License in the LICENSE file, or at:
 | |
|  
 | |
|  http://www.apache.org/licenses/LICENSE-2.0
 | |
|  
 | |
|  Unless required by applicable law or agreed to in writing, software
 | |
|  distributed under the License is distributed on an "AS IS" BASIS,
 | |
|  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
|  See the License for the specific language governing permissions and
 | |
|  limitations under the License.
 | |
|  */
 | |
| 
 | |
| #import "TUITableView.h"
 | |
| #import "TUITableView+Cell.h"
 | |
| #import "TUITableViewSectionHeader.h"
 | |
| #import "TUINSView.h"
 | |
| 
 | |
| // header views need to be above the cells at all times
 | |
| #define HEADER_Z_POSITION 1000 
 | |
| 
 | |
| typedef struct {
 | |
| 	CGFloat offset; // from beginning of section
 | |
| 	CGFloat height;
 | |
| } TUITableViewRowInfo;
 | |
| 
 | |
| @interface TUITableViewSection : NSObject
 | |
| {
 | |
| 	__unsafe_unretained TUITableView  *_tableView;   // weak
 | |
| 	TUIView              *_headerView;  // Not reusable (similar to UITableView)
 | |
| 	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;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Obtain the section header view.
 | |
|  * 
 | |
|  * The section header view is created lazily via the data source when this
 | |
|  * method is first called.
 | |
|  * 
 | |
|  * @return section header view
 | |
|  */
 | |
| - (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]; // must call super
 | |
| }
 | |
| 
 | |
| - (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;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Update section info
 | |
|  * 
 | |
|  * The previous section info is released and new section info is created.
 | |
|  */
 | |
| - (void)_updateSectionInfo {
 | |
|   
 | |
|   if(_sectionInfo != nil){
 | |
|     
 | |
|     // remove any visible headers, they should be re-added when the table is laid out
 | |
|     for(TUITableViewSection *section in _sectionInfo){
 | |
|       TUIView *headerView;
 | |
|       if((headerView = [section headerView]) != nil){
 | |
|         [headerView removeFromSuperview];
 | |
|       }
 | |
|     }
 | |
|     
 | |
|     // clear visible section headers
 | |
|     [_visibleSectionHeaders removeAllIndexes];
 | |
|     // clear the section info array
 | |
| 	_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;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Obtain the header view for the specified section
 | |
|  * 
 | |
|  * If the section has no header, nil is returned.
 | |
|  * 
 | |
|  * @param section the section
 | |
|  * @return section header
 | |
|  */
 | |
| - (TUIView *)headerViewForSection:(NSInteger)section {
 | |
|   if(section >= 0 && section < [_sectionInfo count]){
 | |
|     return [(TUITableViewSection *)[_sectionInfo objectAtIndex:section] headerView];
 | |
|   }else{
 | |
|     return nil;
 | |
|   }
 | |
| }
 | |
| 
 | |
| - (TUITableViewCell *)cellForRowAtIndexPath:(TUIFastIndexPath *)indexPath // returns nil if cell is not visible or index path is out of range
 | |
| {
 | |
| 	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;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Obtain the indexes of sections which intersect @p rect.
 | |
|  * 
 | |
|  * @param rect the rect
 | |
|  * @return intersecting sections
 | |
|  */
 | |
| - (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;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Obtain the indexes of sections whose header views intersect @p rect.
 | |
|  * 
 | |
|  * @param rect the rect
 | |
|  * @return intersecting sections
 | |
|  */
 | |
| - (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 {
 | |
| 				// not visible
 | |
| 			}
 | |
| 		}
 | |
| 		++sectionIndex;
 | |
| 	}
 | |
| 	return indexPaths;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Obtain the index path of the row at the specified point
 | |
|  * 
 | |
|  * If the point is not valid or no row exists at that point, nil is
 | |
|  * returned.
 | |
|  * 
 | |
|  * @param point location in the table view
 | |
|  * @return index path of the row at @p point
 | |
|  */
 | |
| - (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;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Obtain the index path of the row at the specified y-coordinate offset
 | |
|  * 
 | |
|  * Unlike #indexPathForRowAtPoint:, this method does not consider the x-coordinate.
 | |
|  * If the offset is not valid or no row exists at that offset, nil is returned.
 | |
|  * 
 | |
|  * @param offset y-coordinate offset in the table view
 | |
|  * @return index path of the row at @p offset
 | |
|  */
 | |
| - (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;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Obtain the index of a section whose header is at the specified point
 | |
|  * 
 | |
|  * If the point is not valid or no header exists at that point, a negative value
 | |
|  * is returned.
 | |
|  * 
 | |
|  * @param point location in the table view
 | |
|  * @return index of the section whose header is at @p point
 | |
|  */
 | |
| - (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;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Obtain the index of a section whose header is at the specified y-coordinate offset
 | |
|  * 
 | |
|  * Unlike #indexOfSectionWithHeaderAtPoint:, this method does not consider the x-coordinate.
 | |
|  * If the offset is not valid or no header exists at that offset, a negative value
 | |
|  * is returned.
 | |
|  * 
 | |
|  * @param offset y-coordinate offset in the table view
 | |
|  * @return index of the section whose header is at @p offset
 | |
|  */
 | |
| - (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;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Enumerate index paths
 | |
|  * @see #enumerateIndexPathsFromIndexPath:toIndexPath:withOptions:usingBlock:
 | |
|  */
 | |
| - (void)enumerateIndexPathsUsingBlock:(void (^)(TUIFastIndexPath *indexPath, BOOL *stop))block {
 | |
|   [self enumerateIndexPathsFromIndexPath:nil toIndexPath:nil withOptions:0 usingBlock:block];
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Enumerate index paths
 | |
|  * @see #enumerateIndexPathsFromIndexPath:toIndexPath:withOptions:usingBlock:
 | |
|  */
 | |
| - (void)enumerateIndexPathsWithOptions:(NSEnumerationOptions)options usingBlock:(void (^)(TUIFastIndexPath *indexPath, BOOL *stop))block {
 | |
|   [self enumerateIndexPathsFromIndexPath:nil toIndexPath:nil withOptions:options usingBlock:block];
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Enumerate index paths
 | |
|  * 
 | |
|  * The provided block is repeatedly invoked with each valid index path between
 | |
|  * the specified bounds.  Both bounding index paths are inclusive.
 | |
|  * 
 | |
|  * @param fromIndexPath the index path to begin enumerating at or nil to begin at the first index path
 | |
|  * @param toIndexPath the index path to stop enumerating at or nil to stop at the last index path
 | |
|  * @param options enumeration options (not currently supported; pass 0)
 | |
|  * @param block the block to enumerate with
 | |
|  */
 | |
| - (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; // start at the lower bound row for the first iteration...
 | |
|   for(NSInteger i = sectionLowerBound; i < [self numberOfSections] && i <= sectionUpperBound /* inclusive */; i++){
 | |
|     NSInteger rowCount = [self numberOfRowsInSection:i];
 | |
|     for(NSInteger j = irow; j < rowCount && j <= ((rowUpperBound < 0 || i < sectionUpperBound) ? rowCount - 1 : rowUpperBound) /* inclusive */; j++){
 | |
|       BOOL stop = FALSE;
 | |
|       block([TUIFastIndexPath indexPathForRow:j inSection:i], &stop);
 | |
|       if(stop) return;
 | |
|     }
 | |
|     irow = 0; // ...then use zero for subsequent iterations
 | |
|   }
 | |
|   
 | |
| }
 | |
| 
 | |
| - (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; // prevent the auto-scroll-to-top during the first layout
 | |
| 	[super setContentOffset:p];
 | |
| 	
 | |
| 	// if we're currently dragging we need to update the drag operation since the content under
 | |
| 	// the mouse has moved; we just call update again with the last mouse location
 | |
|   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)) {
 | |
| 	  
 | |
| 		// save scroll position
 | |
| 		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]; // clean up any previous section info and recreate it
 | |
| 		self.contentSize = CGSizeMake(self.bounds.size.width, _contentHeight);
 | |
| 		
 | |
| 		_lastSize = bounds.size;
 | |
| 		
 | |
| 		if(!_tableFlags.didFirstLayout) {
 | |
| 			_tableFlags.didFirstLayout = 1;
 | |
| 			[self scrollToTopAnimated:NO];
 | |
| 		}
 | |
| 		
 | |
| 		// restore scroll position
 | |
| 		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; // needs visible cells to be redisplayed
 | |
| 	}
 | |
| 	
 | |
| 	return NO; // just need to do the recycling
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Layout header views for sections which have one.
 | |
|  */
 | |
| - (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];
 | |
| 	
 | |
| 	// update the placement of all visible headers
 | |
| 	__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) {
 | |
| 					// check if this header needs to be pinned
 | |
| 					if(CGRectGetMaxY(headerFrame) > CGRectGetMaxY(visible)) {
 | |
| 						headerFrame.origin.y = CGRectGetMaxY(visible) - headerFrame.size.height;
 | |
| 						pinnedHeader = section.headerView;
 | |
| 						// if the header is a TUITableViewSectionHeader notify it of it's pinned state
 | |
| 						if([section.headerView isKindOfClass:[TUITableViewSectionHeader class]]){
 | |
| 							((TUITableViewSectionHeader *)section.headerView).pinnedToViewport = TRUE;
 | |
| 						}
 | |
| 					}else if((pinnedHeader != nil) && (CGRectGetMaxY(headerFrame) > pinnedHeader.frame.origin.y)) {
 | |
| 						// this header is intersecting with the pinned header, so we push the pinned header upwards.
 | |
| 						CGRect pinnedHeaderFrame = pinnedHeader.frame;
 | |
| 						pinnedHeaderFrame.origin.y = CGRectGetMaxY(headerFrame);
 | |
| 						pinnedHeader.frame = pinnedHeaderFrame;
 | |
| 						// if the header is a TUITableViewSectionHeader notify it of it's pinned state
 | |
| 						if([section.headerView isKindOfClass:[TUITableViewSectionHeader class]]){
 | |
| 							((TUITableViewSectionHeader *)section.headerView).pinnedToViewport = FALSE;
 | |
| 						}
 | |
| 					}else{
 | |
| 						// if the header is a TUITableViewSectionHeader notify it of it's pinned state
 | |
| 						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];
 | |
| 	}];
 | |
| 	
 | |
| 	// remove offscreen headers
 | |
| 	[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) {
 | |
| 		// update remaining visible cells if needed
 | |
| 		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];
 | |
| 	
 | |
| 	// Example:
 | |
| 	// old:            0 1 2 3 4 5 6 7
 | |
| 	// new:                2 3 4 5 6 7 8 9
 | |
| 	// to remove:      0 1
 | |
| 	// to add:                         8 9
 | |
| 	
 | |
| 	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];
 | |
| 	
 | |
| 	// remove offscreen cells
 | |
| 	for(TUIFastIndexPath *i in indexPathsToRemove) {
 | |
| 		TUITableViewCell *cell = [self cellForRowAtIndexPath:i];
 | |
| 		// don't reuse the dragged cell
 | |
| 		if(_dragToReorderCell == nil || ![cell isEqual:_dragToReorderCell]){
 | |
|       [self _enqueueReusableCell:cell];
 | |
|       [cell removeFromSuperview];
 | |
|       [_visibleItems removeObjectForKey:i];
 | |
|     }
 | |
| 	}
 | |
| 	
 | |
| 	// add new cells
 | |
| 	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]) {
 | |
| 			  // only make cells first responder if they accept it
 | |
| 			  if([cell acceptsFirstResponder]){
 | |
| 			    [self.nsWindow makeFirstResponderIfNotAlreadyInResponderChain:cell withFutureRequestToken:_futureMakeFirstResponderToken];
 | |
| 			  }
 | |
| 				_indexPathShouldBeFirstResponder = nil;
 | |
| 			}
 | |
| 			
 | |
| 			[_visibleItems setObject:cell forKey:i];
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
|   // if we have a dragged cell, make sure it's on top of the newly added cells
 | |
|   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) {
 | |
| 				// show
 | |
| 				_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) {
 | |
| 				// show
 | |
| 				_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
 | |
| {
 | |
|   
 | |
|   // notify our delegate we're about to reload the table
 | |
|   if(self.delegate != nil && [self.delegate respondsToSelector:@selector(tableViewWillReloadData:)]){
 | |
|     [self.delegate tableViewWillReloadData:self];
 | |
|   }
 | |
| 	
 | |
| 	_selectedIndexPath = nil;
 | |
|   
 | |
| 	// need to recycle all visible cells, have them be regenerated on layoutSubviews
 | |
| 	// because the same cells might have different content
 | |
| 	for(TUIFastIndexPath *i in _visibleItems) {
 | |
| 		TUITableViewCell *cell = [_visibleItems objectForKey:i];
 | |
| 		[self _enqueueReusableCell:cell];
 | |
| 		[cell removeFromSuperview];
 | |
| 	}
 | |
| 	
 | |
| 	// if we have a dragged cell, clear it
 | |
| 	_dragToReorderCell = nil;
 | |
| 	
 | |
| 	// clear visible cells
 | |
| 	[_visibleItems removeAllObjects];
 | |
| 	
 | |
| 	// remove any visible headers, they should be re-added when the table is laid out
 | |
| 	for(TUITableViewSection *section in _sectionInfo){
 | |
| 	  TUIView *headerView;
 | |
| 	  if((headerView = [section headerView]) != nil){
 | |
| 	    [headerView removeFromSuperview];
 | |
| 	  }
 | |
| 	}
 | |
| 	
 | |
| 	// clear visible section headers
 | |
| 	[_visibleSectionHeaders removeAllIndexes];
 | |
| 	
 | |
| 	_sectionInfo = nil; // will be regenerated on next layout
 | |
| 	
 | |
| 	[self layoutSubviews];
 | |
| 	
 | |
|   // notify our delegate the table view has been reloaded
 | |
|   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]; // this will munge with the contentOffset
 | |
| 			[self _layoutSectionHeaders:visibleCellsNeedRelayout];
 | |
| 			[self _layoutCells:visibleCellsNeedRelayout];
 | |
| 			
 | |
| 			if(_tableFlags.derepeaterEnabled)
 | |
| 				[self _updateDerepeaterViews];
 | |
| 			
 | |
| 			[CATransaction commit];
 | |
| 		}];
 | |
| 		
 | |
| 		_tableFlags.layoutSubviewsReentrancyGuard = 0;
 | |
| 	} else {
 | |
| //		NSLog(@"trying to nest...");
 | |
| 	}
 | |
| }
 | |
| 
 | |
| - (void)reloadLayout
 | |
| {
 | |
| 	_sectionInfo = nil; // will be regenerated on next layout
 | |
| 	
 | |
| 	[self _preLayoutCells];
 | |
| 	[super layoutSubviews]; // this will munge with the contentOffset
 | |
| 	[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];
 | |
| 	
 | |
| 	// when the target index path section has a header view, add its height to
 | |
| 	// the height of our row to prevent the selected row from being overlapped
 | |
| 	// by the pinned header
 | |
|   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:
 | |
| 			// do nothing
 | |
| 			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];
 | |
| 	// only cells that accept first responder should be made first responder
 | |
| 	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];
 | |
| //	if([indexPath isEqual:oldIndexPath]) {
 | |
| //		// just scroll to visible
 | |
| //	} else {
 | |
| 		[self deselectRowAtIndexPath:[self indexPathForSelectedRow] animated:animated];
 | |
| 		
 | |
| 		TUITableViewCell *cell = [self cellForRowAtIndexPath:indexPath]; // may be nil
 | |
| 		[cell setSelected:YES animated:animated];
 | |
| 		 // should already be nil
 | |
| 		_selectedIndexPath = indexPath;
 | |
| 		[cell setNeedsDisplay];
 | |
| 		
 | |
| 		// only notify when the selection actually changes
 | |
| 		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]) {
 | |
|     // only make cell first responder if the table view already is first responder
 | |
|     [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]; // may be nil
 | |
| 		
 | |
| 		[cell setSelected:NO animated:animated];
 | |
| 		_selectedIndexPath = nil;
 | |
| 		[cell setNeedsDisplay];
 | |
| 		
 | |
| 		// only notify when the selection actually changes
 | |
|     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
 | |
| {
 | |
| 	// no selection or selected cell not visible and this is not repeative key press
 | |
| 	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
 |