![]() |
API 0.9.5
|
00001 /* 00002 * CPTableColumn.j 00003 * AppKit 00004 * 00005 * Created by Francisco Tolmasky. 00006 * Copyright 2009, 280 North, Inc. 00007 * 00008 * This library is free software; you can redistribute it and/or 00009 * modify it under the terms of the GNU Lesser General Public 00010 * License as published by the Free Software Foundation; either 00011 * version 2.1 of the License, or (at your option) any later version. 00012 * 00013 * This library is distributed in the hope that it will be useful, 00014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00016 * Lesser General Public License for more details. 00017 * 00018 * You should have received a copy of the GNU Lesser General Public 00019 * License along with this library; if not, write to the Free Software 00020 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00021 */ 00022 00023 00024 00025 00026 CPTableColumnNoResizing = 0; 00027 CPTableColumnAutoresizingMask = 1 << 0; 00028 CPTableColumnUserResizingMask = 1 << 1; 00029 00041 @implementation CPTableColumn : CPObject 00042 { 00043 CPTableView _tableView; 00044 CPView _headerView; 00045 CPView _dataView; 00046 Object _dataViewData; 00047 00048 float _width; 00049 float _minWidth; 00050 float _maxWidth; 00051 unsigned _resizingMask; 00052 00053 id _identifier; 00054 BOOL _isEditable; 00055 CPSortDescriptor _sortDescriptorPrototype; 00056 BOOL _isHidden; 00057 CPString _headerToolTip; 00058 00059 BOOL _disableResizingPosting; 00060 } 00061 00065 - (id)init 00066 { 00067 return [self initWithIdentifier:@""]; 00068 } 00069 00074 - (id)initWithIdentifier:(id)anIdentifier 00075 { 00076 self = [super init]; 00077 00078 if (self) 00079 { 00080 _dataViewData = { }; 00081 00082 _width = 100.0; 00083 _minWidth = 10.0; 00084 _maxWidth = 1000000.0; 00085 _resizingMask = CPTableColumnAutoresizingMask | CPTableColumnUserResizingMask; 00086 _disableResizingPosting = NO; 00087 00088 [self setIdentifier:anIdentifier]; 00089 00090 var header = [[_CPTableColumnHeaderView alloc] initWithFrame:CGRectMakeZero()]; 00091 [self setHeaderView:header]; 00092 00093 [self setDataView:[CPTextField new]]; 00094 } 00095 00096 return self; 00097 } 00098 00102 - (void)setTableView:(CPTableView)aTableView 00103 { 00104 _tableView = aTableView; 00105 } 00106 00110 - (CPTableView)tableView 00111 { 00112 return _tableView; 00113 } 00114 00125 - (int)_tryToResizeToWidth:(int)width 00126 { 00127 var min = [self minWidth], 00128 max = [self maxWidth], 00129 newWidth = ROUND(MIN(MAX(width, min), max)); 00130 00131 [self setWidth:newWidth]; 00132 00133 return newWidth - width; 00134 } 00135 00143 - (void)setWidth:(float)aWidth 00144 { 00145 aWidth = +aWidth; 00146 00147 if (_width === aWidth) 00148 return; 00149 00150 var newWidth = MIN(MAX(aWidth, [self minWidth]), [self maxWidth]); 00151 00152 if (_width === newWidth) 00153 return; 00154 00155 var oldWidth = _width; 00156 00157 _width = newWidth; 00158 00159 var tableView = [self tableView]; 00160 00161 if (tableView) 00162 { 00163 var index = [[tableView tableColumns] indexOfObjectIdenticalTo:self], 00164 dirtyTableColumnRangeIndex = tableView._dirtyTableColumnRangeIndex; 00165 00166 if (dirtyTableColumnRangeIndex < 0) 00167 tableView._dirtyTableColumnRangeIndex = index; 00168 else 00169 tableView._dirtyTableColumnRangeIndex = MIN(index, tableView._dirtyTableColumnRangeIndex); 00170 00171 var rows = tableView._exposedRows, 00172 columns = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(index, [tableView._exposedColumns lastIndex] - index + 1)]; 00173 00174 // FIXME: Would be faster with some sort of -setNeedsDisplayInColumns: that updates a dirtyTableColumnForDisplay cache; then marked columns would relayout their data views at display time. 00175 [tableView _layoutDataViewsInRows:rows columns:columns]; 00176 [tableView tile]; 00177 00178 if (!_disableResizingPosting) 00179 [self _postDidResizeNotificationWithOldWidth:oldWidth]; 00180 } 00181 } 00182 00186 - (float)width 00187 { 00188 return _width; 00189 } 00190 00195 - (void)setMinWidth:(float)aMinWidth 00196 { 00197 aMinWidth = +aMinWidth; 00198 00199 if (_minWidth === aMinWidth) 00200 return; 00201 00202 _minWidth = aMinWidth; 00203 00204 var width = [self width], 00205 newWidth = MAX(width, [self minWidth]); 00206 00207 if (width !== newWidth) 00208 [self setWidth:newWidth]; 00209 } 00210 00214 - (float)minWidth 00215 { 00216 return _minWidth; 00217 } 00218 00223 - (void)setMaxWidth:(float)aMaxWidth 00224 { 00225 aMaxWidth = +aMaxWidth; 00226 00227 if (_maxWidth === aMaxWidth) 00228 return; 00229 00230 _maxWidth = aMaxWidth; 00231 00232 var width = [self width], 00233 newWidth = MIN(width, [self maxWidth]); 00234 00235 if (width !== newWidth) 00236 [self setWidth:newWidth]; 00237 } 00238 00242 - (float)maxWidth 00243 { 00244 return _maxWidth; 00245 } 00246 00258 - (void)setResizingMask:(unsigned)aResizingMask 00259 { 00260 _resizingMask = aResizingMask; 00261 } 00262 00263 00267 - (unsigned)resizingMask 00268 { 00269 return _resizingMask; 00270 } 00271 00275 - (void)sizeToFit 00276 { 00277 var width = _CGRectGetWidth([_headerView frame]); 00278 00279 if (width < [self minWidth]) 00280 [self setMinWidth:width]; 00281 else if (width > [self maxWidth]) 00282 [self setMaxWidth:width] 00283 00284 if (_width !== width) 00285 [self setWidth:width]; 00286 } 00287 00288 00298 - (void)setHeaderView:(CPView)aView 00299 { 00300 if (!aView) 00301 [CPException raise:CPInvalidArgumentException reason:@"Attempt to set nil header view on " + [self description]]; 00302 00303 _headerView = aView; 00304 00305 var tableHeaderView = [_tableView headerView]; 00306 00307 [tableHeaderView setNeedsLayout]; 00308 [tableHeaderView setNeedsDisplay:YES]; 00309 } 00310 00317 - (CPView)headerView 00318 { 00319 return _headerView; 00320 } 00321 00386 - (void)setDataView:(CPView)aView 00387 { 00388 if (_dataView) 00389 _dataViewData[[_dataView UID]] = nil; 00390 00391 [aView setThemeState:CPThemeStateTableDataView]; 00392 00393 _dataView = aView; 00394 _dataViewData[[aView UID]] = [CPKeyedArchiver archivedDataWithRootObject:aView]; 00395 } 00396 00397 - (CPView)dataView 00398 { 00399 return _dataView; 00400 } 00401 00402 /* 00403 Returns the CPView object used by the CPTableView to draw values for the receiver. 00404 00405 By default, this method just calls dataView. Subclassers can override if they need to 00406 potentially use different "cells" or dataViews for different rows. Subclasses should expect this method 00407 to be invoked with row equal to -1 in cases where no actual row is involved but the table 00408 view needs to get some generic cell info. 00409 */ 00410 - (id)dataViewForRow:(int)aRowIndex 00411 { 00412 return [self dataView]; 00413 } 00414 00418 - (id)_newDataViewForRow:(int)aRowIndex 00419 { 00420 var dataView = [self dataViewForRow:aRowIndex], 00421 dataViewUID = [dataView UID]; 00422 00423 var x = [self tableView]._cachedDataViews[dataViewUID]; 00424 if (x && x.length) 00425 return x.pop(); 00426 00427 // if we haven't cached an archive of the data view, do it now 00428 if (!_dataViewData[dataViewUID]) 00429 _dataViewData[dataViewUID] = [CPKeyedArchiver archivedDataWithRootObject:dataView]; 00430 00431 // unarchive the data view cache 00432 var newDataView = [CPKeyedUnarchiver unarchiveObjectWithData:_dataViewData[dataViewUID]]; 00433 newDataView.identifier = dataViewUID; 00434 00435 // make sure only we have control over the size and placement 00436 [newDataView setAutoresizingMask:CPViewNotSizable]; 00437 00438 return newDataView; 00439 } 00440 00441 //Setting the Identifier 00442 00446 - (void)setIdentifier:(id)anIdentifier 00447 { 00448 _identifier = anIdentifier; 00449 } 00450 00454 - (id)identifier 00455 { 00456 return _identifier; 00457 } 00458 00459 //Controlling Editability 00460 00464 - (void)setEditable:(BOOL)shouldBeEditable 00465 { 00466 _isEditable = shouldBeEditable; 00467 } 00468 00473 - (BOOL)isEditable 00474 { 00475 return _isEditable; 00476 } 00477 00481 - (void)setSortDescriptorPrototype:(CPSortDescriptor)aSortDescriptor 00482 { 00483 _sortDescriptorPrototype = aSortDescriptor; 00484 } 00485 00489 - (CPSortDescriptor)sortDescriptorPrototype 00490 { 00491 return _sortDescriptorPrototype; 00492 } 00493 00498 - (void)setHidden:(BOOL)shouldBeHidden 00499 { 00500 shouldBeHidden = !!shouldBeHidden 00501 if (_isHidden === shouldBeHidden) 00502 return; 00503 00504 _isHidden = shouldBeHidden; 00505 00506 [[self headerView] setHidden:shouldBeHidden]; 00507 [[self tableView] _tableColumnVisibilityDidChange:self]; 00508 } 00509 00513 - (BOOL)isHidden 00514 { 00515 return _isHidden; 00516 } 00517 00518 //Setting Tool Tips 00519 00524 - (void)setHeaderToolTip:(CPString)aToolTip 00525 { 00526 _headerToolTip = aToolTip; 00527 } 00528 00532 - (CPString)headerToolTip 00533 { 00534 return _headerToolTip; 00535 } 00536 00540 - (void)_postDidResizeNotificationWithOldWidth:(float)oldWidth 00541 { 00542 [[self tableView] _didResizeTableColumn:self]; 00543 00544 [[CPNotificationCenter defaultCenter] 00545 postNotificationName:CPTableViewColumnDidResizeNotification 00546 object:[self tableView] 00547 userInfo:[CPDictionary dictionaryWithObjects:[self, oldWidth] forKeys:[@"CPTableColumn", "CPOldWidth"]]]; 00548 } 00549 00550 @end 00551 @implementation CPTableColumnValueBinder : CPBinder 00552 { 00553 id __doxygen__; 00554 } 00555 00556 - (void)setValueFor:(CPString)aBinding 00557 { 00558 var tableView = [_source tableView], 00559 column = [[tableView tableColumns] indexOfObjectIdenticalTo:_source], 00560 rowIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [tableView numberOfRows])], 00561 columnIndexes = [CPIndexSet indexSetWithIndex:column]; 00562 00563 [tableView reloadDataForRowIndexes:rowIndexes columnIndexes:columnIndexes]; 00564 } 00565 00566 @end 00567 00568 @implementation CPTableColumn (Bindings) 00569 00570 + (id)_binderClassForBinding:(CPString)aBinding 00571 { 00572 if (aBinding == CPValueBinding) 00573 return [CPTableColumnValueBinder class]; 00574 00575 return [super _binderClassForBinding:aBinding]; 00576 } 00577 00586 - (void)bind:(CPString)aBinding toObject:(id)anObject withKeyPath:(CPString)aKeyPath options:(CPDictionary)options 00587 { 00588 [super bind:aBinding toObject:anObject withKeyPath:aKeyPath options:options]; 00589 00590 if (![aBinding isEqual:@"someListOfExceptedBindings(notAcceptedBindings)"]) 00591 { 00592 // Bind the table to the array controller this column is bound to. 00593 // Note that anObject might not be the array controller. E.g. the keypath could be something like 00594 // somePathTo.anArrayController.arrangedObjects.aKey. Cocoa doesn't support this but it is consistent 00595 // and it makes sense. 00596 var acIndex = aKeyPath.lastIndexOf("arrangedObjects."), 00597 arrayController = anObject; 00598 00599 if (acIndex > 1) 00600 { 00601 var firstPart = aKeyPath.substring(0, acIndex - 1); 00602 arrayController = [anObject valueForKeyPath:firstPart]; 00603 } 00604 00605 [[self tableView] _establishBindingsIfUnbound:arrayController]; 00606 } 00607 } 00608 00612 - (void)_prepareDataView:(CPView)aDataView forRow:(unsigned)aRow 00613 { 00614 var bindingsDictionary = [CPBinder allBindingsForObject:self], 00615 keys = [bindingsDictionary allKeys]; 00616 00617 for (var i = 0, count = [keys count]; i < count; i++) 00618 { 00619 var bindingName = keys[i], 00620 bindingPath = [aDataView _replacementKeyPathForBinding:bindingName], 00621 binding = [bindingsDictionary objectForKey:bindingName], 00622 bindingInfo = binding._info, 00623 destination = [bindingInfo objectForKey:CPObservedObjectKey], 00624 keyPath = [bindingInfo objectForKey:CPObservedKeyPathKey], 00625 dotIndex = keyPath.lastIndexOf("."), 00626 value; 00627 00628 if (dotIndex === CPNotFound) 00629 value = [[destination valueForKeyPath:keyPath] objectAtIndex:aRow]; 00630 else 00631 { 00632 /* 00633 Optimize the prototypical use case where the key path describes a value 00634 in an array. Without this optimization, we call CPArray's valueForKey 00635 which generates as many values as objects in the array, of which we then 00636 pick one and throw away the rest. 00637 00638 The optimization is to get the array and access the value directly. This 00639 turns the operation into a single access regardless of how long the model 00640 array is. 00641 */ 00642 00643 var firstPart = keyPath.substring(0, dotIndex), 00644 secondPart = keyPath.substring(dotIndex + 1), 00645 firstValue = [destination valueForKeyPath:firstPart]; 00646 00647 if ([firstValue isKindOfClass:CPArray]) 00648 value = [[firstValue objectAtIndex:aRow] valueForKeyPath:secondPart]; 00649 else 00650 value = [[firstValue valueForKeyPath:secondPart] objectAtIndex:aRow]; 00651 } 00652 00653 value = [binding transformValue:value withOptions:[bindingInfo objectForKey:CPOptionsKey]]; 00654 [aDataView setValue:value forKey:@"objectValue"]; 00655 } 00656 } 00657 00661 - (void)_reverseSetDataView:(CPView)aDataView forRow:(unsigned)aRow 00662 { 00663 var bindingsDictionary = [CPBinder allBindingsForObject:self], 00664 keys = [bindingsDictionary allKeys], 00665 newValue = [aDataView valueForKey:@"objectValue"]; 00666 00667 for (var i = 0, count = [keys count]; i < count; i++) 00668 { 00669 var bindingName = keys[i], 00670 bindingPath = [aDataView _replacementKeyPathForBinding:bindingName], 00671 binding = [bindingsDictionary objectForKey:bindingName], 00672 bindingInfo = binding._info, 00673 destination = [bindingInfo objectForKey:CPObservedObjectKey], 00674 keyPath = [bindingInfo objectForKey:CPObservedKeyPathKey], 00675 dotIndex = keyPath.lastIndexOf("."); 00676 00677 if (dotIndex === CPNotFound) 00678 [[destination valueForKeyPath:keyPath] replaceObjectAtIndex:aRow withObject:newValue]; 00679 else 00680 { 00681 var firstPart = keyPath.substring(0, dotIndex), 00682 secondPart = keyPath.substring(dotIndex + 1), 00683 firstValue = [destination valueForKeyPath:firstPart]; 00684 00685 if ([firstValue isKindOfClass:CPArray]) 00686 [[firstValue objectAtIndex:aRow] setValue:newValue forKeyPath:secondPart]; 00687 else 00688 [[firstValue valueForKeyPath:secondPart] replaceObjectAtIndex:aRow withObject:newValue]; 00689 } 00690 } 00691 } 00692 00693 //- (void)objectValue 00694 //{ 00695 // return nil; 00696 //} 00697 00698 @end 00699 00700 var CPTableColumnIdentifierKey = @"CPTableColumnIdentifierKey", 00701 CPTableColumnHeaderViewKey = @"CPTableColumnHeaderViewKey", 00702 CPTableColumnDataViewKey = @"CPTableColumnDataViewKey", 00703 CPTableColumnWidthKey = @"CPTableColumnWidthKey", 00704 CPTableColumnMinWidthKey = @"CPTableColumnMinWidthKey", 00705 CPTableColumnMaxWidthKey = @"CPTableColumnMaxWidthKey", 00706 CPTableColumnResizingMaskKey = @"CPTableColumnResizingMaskKey", 00707 CPTableColumnIsHiddenKey = @"CPTableColumnIsHiddenKey", 00708 CPSortDescriptorPrototypeKey = @"CPSortDescriptorPrototypeKey", 00709 CPTableColumnIsEditableKey = @"CPTableColumnIsEditableKey"; 00710 00711 @implementation CPTableColumn (CPCoding) 00712 00716 - (id)initWithCoder:(CPCoder)aCoder 00717 { 00718 self = [super init]; 00719 00720 if (self) 00721 { 00722 _dataViewData = { }; 00723 00724 _width = [aCoder decodeFloatForKey:CPTableColumnWidthKey]; 00725 _minWidth = [aCoder decodeFloatForKey:CPTableColumnMinWidthKey]; 00726 _maxWidth = [aCoder decodeFloatForKey:CPTableColumnMaxWidthKey]; 00727 00728 [self setIdentifier:[aCoder decodeObjectForKey:CPTableColumnIdentifierKey]]; 00729 [self setHeaderView:[aCoder decodeObjectForKey:CPTableColumnHeaderViewKey]]; 00730 [self setDataView:[aCoder decodeObjectForKey:CPTableColumnDataViewKey]]; 00731 [self setHeaderView:[aCoder decodeObjectForKey:CPTableColumnHeaderViewKey]]; 00732 00733 _resizingMask = [aCoder decodeIntForKey:CPTableColumnResizingMaskKey]; 00734 _isHidden = [aCoder decodeBoolForKey:CPTableColumnIsHiddenKey]; 00735 _isEditable = [aCoder decodeBoolForKey:CPTableColumnIsEditableKey]; 00736 00737 _sortDescriptorPrototype = [aCoder decodeObjectForKey:CPSortDescriptorPrototypeKey]; 00738 } 00739 00740 return self; 00741 } 00742 00746 - (void)encodeWithCoder:(CPCoder)aCoder 00747 { 00748 [aCoder encodeObject:_identifier forKey:CPTableColumnIdentifierKey]; 00749 00750 [aCoder encodeFloat:_width forKey:CPTableColumnWidthKey]; 00751 [aCoder encodeFloat:_minWidth forKey:CPTableColumnMinWidthKey]; 00752 [aCoder encodeFloat:_maxWidth forKey:CPTableColumnMaxWidthKey]; 00753 00754 [aCoder encodeObject:_headerView forKey:CPTableColumnHeaderViewKey]; 00755 [aCoder encodeObject:_dataView forKey:CPTableColumnDataViewKey]; 00756 00757 [aCoder encodeObject:_resizingMask forKey:CPTableColumnResizingMaskKey]; 00758 [aCoder encodeBool:_isHidden forKey:CPTableColumnIsHiddenKey]; 00759 [aCoder encodeBool:_isEditable forKey:CPTableColumnIsEditableKey]; 00760 00761 [aCoder encodeObject:_sortDescriptorPrototype forKey:CPSortDescriptorPrototypeKey]; 00762 } 00763 00764 @end 00765 00766 @implementation CPTableColumn (NSInCompatibility) 00770 - (void)setHeaderCell:(CPView)aView 00771 { 00772 [CPException raise:CPUnsupportedMethodException 00773 reason:@"setHeaderCell: is not supported. Use -setHeaderView:aView instead."]; 00774 } 00775 00779 - (CPView)headerCell 00780 { 00781 [CPException raise:CPUnsupportedMethodException 00782 reason:@"headCell is not supported. Use -headerView instead."]; 00783 } 00784 00788 - (void)setDataCell:(CPView)aView 00789 { 00790 [CPException raise:CPUnsupportedMethodException 00791 reason:@"setDataCell: is not supported. Use -setDataView:aView instead."]; 00792 } 00793 00797 - (CPView)dataCell 00798 { 00799 [CPException raise:CPUnsupportedMethodException 00800 reason:@"dataCell is not supported. Use -dataView instead."]; 00801 } 00802 00806 - (id)dataCellForRow:(int)row 00807 { 00808 [CPException raise:CPUnsupportedMethodException 00809 reason:@"dataCellForRow: is not supported. Use -dataViewForRow:row instead."]; 00810 } 00811 00812 @end 00813 00814 @implementation CPTableColumn (CPSynthesizedAccessors) 00815 00819 - (BOOL)disableResizingPosting 00820 { 00821 return _disableResizingPosting; 00822 } 00823 00827 - (void)setDisableResizingPosting:(BOOL)aValue 00828 { 00829 _disableResizingPosting = aValue; 00830 } 00831 00832 @end