API 0.9.5
AppKit/CPTableColumn.j
Go to the documentation of this file.
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
 All Classes Files Functions Variables Defines