/* * 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" var CPTableViewCellPlaceholder = nil; /* 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 { id _dataSource; CPScrollView _scrollView; CPTableHeaderView _headerView; CPArray _tableColumns; unsigned _numberOfRows; unsigned _numberOfColumns; CPArray _tableCells; CPArray _tableColumnViews; CGSize _intercellSpacing; } + (void)initialize { if (self != [CPTableView class]) return; CPTableViewCellPlaceholder = [[CPObject alloc] init]; } - (id)initWithFrame:(CGRect)aFrame { self = [super initWithFrame:aFrame]; if (self) { /*_scrollView = [[CPView alloc] initWithFrame:CPRectMakeZero()]; _headerView = [[CPView alloc] initWithFrame:CPRectMakeZero()]; [_scrollView setBackgroundColor:[CPColor redColor]]; [_headerView setBackgroundColor:[CPColor blueColor]]; [self addSubview:_scrollView]; [self addSubview:_headerView]; [self tile];*/ //[self setBackgroundColor:[CPColor redColor]]; _rowHeight = 17.0; _tableCells = [[CPArray alloc] init]; _tableColumns = [[CPArray alloc] init]; _tableColumnViews = [[CPArray alloc] init]; _intercellSpacing = CPSizeMake(3.0, 2.0); } return self; } /* Returns the table's column height */ - (float)columnHeight { var bounds = [self bounds], height = _numberOfRows * (_rowHeight + _intercellSpacing.height); return CPRectGetHeight(bounds) > height ? CPRectGetHeight(bounds) : height; } - (void)loadTableCellsInRect:(CGRect)aRect { if (!_dataSource) return; // Use a ambitious estimate for our starting row. var rows = CPMakeRange(MAX(Math.floor((CPRectGetMinY(aRect) + _intercellSpacing.height) / (_rowHeight + _intercellSpacing.height)), 0), 1); // Use a conservative estimate for the final row. rows.length = MIN(_numberOfRows, Math.ceil(CPRectGetMaxY(aRect) / (_rowHeight + _intercellSpacing.height))) - rows.location; var columns = CPMakeRange(0, 1); // Iterate through all our columns until we find one that intersects the rect. while (columns.location < _numberOfColumns && !CPRectIntersectsRect([_tableColumnViews[columns.location] frame], aRect)) ++columns.location; // Now iterate through our columns until we find one that doesn't intersect our rect. while (CPMaxRange(columns) < _numberOfColumns && CPRectIntersectsRect([_tableColumnViews[CPMaxRange(columns)] frame], aRect)) ++columns.length; var row = rows.location, column = 0; for (; row < CPMaxRange(rows); ++row) for (column = columns.location; column < CPMaxRange(columns); ++column) { if (!_tableCells[column][row] || _tableCells[column][row] == CPTableViewCellPlaceholder) { _tableCells[column][row] = [[_tableColumns[column] dataCellForRow:row] copy]; [_tableCells[column][row] setFrame:CPRectMake(0.0, row * (_rowHeight + _intercellSpacing.height), [_tableColumns[column] width], _rowHeight)]; //[_tableCells[column][row] setBackgroundColor:[CPColor blueColor]]; [_tableColumnViews[column] addSubview:_tableCells[column][row]]; } [_tableCells[column][row] setObjectValue:[_dataSource tableView:self objectValueForTableColumn:_tableColumns[column] row:row]]; } } // 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 < _numberOfColumns; ++i, total += delta) { var origin = [_tableColumnViews[i] frame].origin; [_tableColumnViews[i] setFrameOrigin:CPPointMake(origin.x + total, origin.y)]; } } if (_intercellSpacing.height != aSize.height) { var i = 0; for (; i < _numberOfColumns; ++i, total += delta) { [_tableColumnViews[i] setFrameSize:CPSizeMake([_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] == CPTableViewCellPlaceholder) continue; [_tableCells[i][j] setFrameOrigin:CPPointMake(0.0, y)]; } } } _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; var row = 0, column = 0; for (; row < _numberOfRows; ++row) for (column = 0; column < _numberOfColumns; ++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 = _numberOfColumns ? CPRectGetMaxX([self rectOfColumn:_numberOfColumns - 1]) + _intercellSpacing.width : 0.0, tableColumnView = [[CPView alloc] initWithFrame:CPRectMake(x, 0.0, [aTableColumn width], [self columnHeight])], tableColumnCells = [[CPArray alloc] init]; [_tableColumns addObject:aTableColumn]; [_tableColumnViews addObject:tableColumnView]; // [tableColumnView setBackgroundColor:[CPColor greenColor]]; [self addSubview:tableColumnView]; [_tableCells addObject:tableColumnCells]; for (; i < _numberOfRows; ++i) _tableCells[_numberOfColumns][i] = CPTableViewCellPlaceholder; ++_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]; [_tabelColumnViews removeObjectAtIndex:index]; // Shift remaining column views to the left. for (; index < _numberOfColumns; ++ 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; } // Getting the dimensions of the table /* Returns the number of columns in the table */ - (int)numberOfColumns { return _numberOfColumns; } /* 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; } - (void)setFrameSize:(CGSize)aSize { var oldColumnHeight = [self columnHeight]; [super setFrameSize:aSize]; var columnHeight = [self columnHeight]; if (columnHeight != oldColumnHeight) { var i = 0; for (; i < _numberOfColumns; ++i) [_tableColumnViews[i] setFrameSize:CPSizeMake([_tableColumns[i] width], columnHeight)]; } [self tile]; } /* 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 sizeToFit]; } } /* 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, aRowIndex * (_rowHeight + _intercellSpacing.height), CPRectGetWidth([self bounds]), _rowHeight); } /* 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 CPRectCreateCopy([_tableColumnViews[aColumnIndex] frame]); } /* Adjusts column widths to make them all visible at once. Same as tile. */ - (void)sizeToFit { [self tile]; } /* Reloads the data from the dataSource. This is an expensive method, so use it lightly. */ - (void)reloadData { _numberOfRows = [_dataSource numberOfRowsInTableView:self]; [self loadTableCellsInRect:[self bounds]]; } @end