API  0.9.6
 All Classes Files Functions Variables Macros Groups Pages
CPTableColumn.j
Go to the documentation of this file.
1 /*
2  * CPTableColumn.j
3  * AppKit
4  *
5  * Created by Francisco Tolmasky.
6  * Copyright 2009, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 
25 
29 
41 @implementation CPTableColumn : CPObject
42 {
43  CPTableView _tableView;
44  CPView _headerView;
45  CPView _dataView;
46  Object _dataViewData;
47 
48  float _width;
49  float _minWidth;
50  float _maxWidth;
51  unsigned _resizingMask;
52 
53  id _identifier;
54  BOOL _isEditable;
55  CPSortDescriptor _sortDescriptorPrototype;
56  BOOL _isHidden;
57  CPString _headerToolTip;
58 
59  BOOL _disableResizingPosting;
60 }
61 
65 - (id)init
66 {
67  return [self initWithIdentifier:@""];
68 }
69 
74 - (id)initWithIdentifier:(id)anIdentifier
75 {
76  self = [super init];
77 
78  if (self)
79  {
80  _dataViewData = { };
81 
82  _width = 100.0;
83  _minWidth = 10.0;
84  _maxWidth = 1000000.0;
86  _disableResizingPosting = NO;
87 
88  [self setIdentifier:anIdentifier];
89 
90  var header = [[_CPTableColumnHeaderView alloc] initWithFrame:CGRectMakeZero()];
91  [self setHeaderView:header];
92 
93  [self setDataView:[CPTextField new]];
94  }
95 
96  return self;
97 }
98 
102 - (void)setTableView:(CPTableView)aTableView
103 {
104  _tableView = aTableView;
105 }
106 
111 {
112  return _tableView;
113 }
114 
125 - (int)_tryToResizeToWidth:(int)width
126 {
127  var min = [self minWidth],
128  max = [self maxWidth],
129  newWidth = ROUND(MIN(MAX(width, min), max));
130 
131  [self setWidth:newWidth];
132 
133  return newWidth - width;
134 }
135 
143 - (void)setWidth:(float)aWidth
144 {
145  aWidth = +aWidth;
146 
147  if (_width === aWidth)
148  return;
149 
150  var newWidth = MIN(MAX(aWidth, [self minWidth]), [self maxWidth]);
151 
152  if (_width === newWidth)
153  return;
154 
155  var oldWidth = _width;
156 
157  _width = newWidth;
158 
159  var tableView = [self tableView];
160 
161  if (tableView)
162  {
163  var index = [[tableView tableColumns] indexOfObjectIdenticalTo:self],
164  dirtyTableColumnRangeIndex = tableView._dirtyTableColumnRangeIndex;
165 
166  if (dirtyTableColumnRangeIndex < 0)
167  tableView._dirtyTableColumnRangeIndex = index;
168  else
169  tableView._dirtyTableColumnRangeIndex = MIN(index, tableView._dirtyTableColumnRangeIndex);
170 
171  var rows = tableView._exposedRows,
172  columns = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(index, [tableView._exposedColumns lastIndex] - index + 1)];
173 
174  // 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.
175  [tableView _layoutDataViewsInRows:rows columns:columns];
176  [tableView tile];
177 
178  if (!_disableResizingPosting)
179  [self _postDidResizeNotificationWithOldWidth:oldWidth];
180  }
181 }
182 
186 - (float)width
187 {
188  return _width;
189 }
190 
195 - (void)setMinWidth:(float)aMinWidth
196 {
197  aMinWidth = +aMinWidth;
198 
199  if (_minWidth === aMinWidth)
200  return;
201 
202  _minWidth = aMinWidth;
203 
204  var width = [self width],
205  newWidth = MAX(width, [self minWidth]);
206 
207  if (width !== newWidth)
208  [self setWidth:newWidth];
209 }
210 
214 - (float)minWidth
215 {
216  return _minWidth;
217 }
218 
223 - (void)setMaxWidth:(float)aMaxWidth
224 {
225  aMaxWidth = +aMaxWidth;
226 
227  if (_maxWidth === aMaxWidth)
228  return;
229 
230  _maxWidth = aMaxWidth;
231 
232  var width = [self width],
233  newWidth = MIN(width, [self maxWidth]);
234 
235  if (width !== newWidth)
236  [self setWidth:newWidth];
237 }
238 
242 - (float)maxWidth
243 {
244  return _maxWidth;
245 }
246 
258 - (void)setResizingMask:(unsigned)aResizingMask
259 {
260  _resizingMask = aResizingMask;
261 }
262 
263 
267 - (unsigned)resizingMask
268 {
269  return _resizingMask;
270 }
271 
275 - (void)sizeToFit
276 {
277  var width = _CGRectGetWidth([_headerView frame]);
278 
279  if (width < [self minWidth])
280  [self setMinWidth:width];
281  else if (width > [self maxWidth])
282  [self setMaxWidth:width]
283 
284  if (_width !== width)
285  [self setWidth:width];
286 }
287 
288 
298 - (void)setHeaderView:(CPView)aView
299 {
300  if (!aView)
301  [CPException raise:CPInvalidArgumentException reason:@"Attempt to set nil header view on " + [self description]];
302 
303  _headerView = aView;
304 
305  var tableHeaderView = [_tableView headerView];
306 
307  [tableHeaderView setNeedsLayout];
308  [tableHeaderView setNeedsDisplay:YES];
309 }
310 
317 - (CPView)headerView
318 {
319  return _headerView;
320 }
321 
386 - (void)setDataView:(CPView)aView
387 {
388  if (_dataView)
389  _dataViewData[[_dataView UID]] = nil;
390 
391  [aView setThemeState:CPThemeStateTableDataView];
392 
393  _dataView = aView;
394  _dataViewData[[aView UID]] = [CPKeyedArchiver archivedDataWithRootObject:aView];
395 }
396 
397 - (CPView)dataView
398 {
399  return _dataView;
400 }
401 
402 /*
403  Returns the CPView object used by the CPTableView to draw values for the receiver.
404 
405  By default, this method just calls dataView. Subclassers can override if they need to
406  potentially use different "cells" or dataViews for different rows. Subclasses should expect this method
407  to be invoked with row equal to -1 in cases where no actual row is involved but the table
408  view needs to get some generic cell info.
409 */
410 - (id)dataViewForRow:(int)aRowIndex
411 {
412  return [self dataView];
413 }
414 
418 - (id)_newDataViewForRow:(int)aRowIndex
419 {
420  var dataView = [self dataViewForRow:aRowIndex],
421  dataViewUID = [dataView UID];
422 
423  var x = [self tableView]._cachedDataViews[dataViewUID];
424  if (x && x.length)
425  return x.pop();
426 
427  // if we haven't cached an archive of the data view, do it now
428  if (!_dataViewData[dataViewUID])
429  _dataViewData[dataViewUID] = [CPKeyedArchiver archivedDataWithRootObject:dataView];
430 
431  // unarchive the data view cache
432  var newDataView = [CPKeyedUnarchiver unarchiveObjectWithData:_dataViewData[dataViewUID]];
433  newDataView.identifier = dataViewUID;
434 
435  // make sure only we have control over the size and placement
436  [newDataView setAutoresizingMask:CPViewNotSizable];
437 
438  return newDataView;
439 }
440 
441 //Setting the Identifier
442 
446 - (void)setIdentifier:(id)anIdentifier
447 {
448  _identifier = anIdentifier;
449 }
450 
454 - (id)identifier
455 {
456  return _identifier;
457 }
458 
459 //Controlling Editability
460 
464 - (void)setEditable:(BOOL)shouldBeEditable
465 {
466  _isEditable = shouldBeEditable;
467 }
468 
473 - (BOOL)isEditable
474 {
475  return _isEditable;
476 }
477 
481 - (void)setSortDescriptorPrototype:(CPSortDescriptor)aSortDescriptor
482 {
483  _sortDescriptorPrototype = aSortDescriptor;
484 }
485 
489 - (CPSortDescriptor)sortDescriptorPrototype
490 {
491  if (_sortDescriptorPrototype)
492  return _sortDescriptorPrototype;
493 
494  var binderClass = [[self class] _binderClassForBinding:CPValueBinding],
495  binding = [binderClass getBinding:CPValueBinding forObject:self];
496 
497  return [binding _defaultSortDescriptorPrototype];
498 }
499 
504 - (void)setHidden:(BOOL)shouldBeHidden
505 {
506  shouldBeHidden = !!shouldBeHidden
507 
508  if (_isHidden === shouldBeHidden)
509  return;
510 
511  _isHidden = shouldBeHidden;
512 
513  [[self headerView] setHidden:shouldBeHidden];
514  [[self tableView] _tableColumnVisibilityDidChange:self];
515 }
516 
520 - (BOOL)isHidden
521 {
522  return _isHidden;
523 }
524 
525 //Setting Tool Tips
526 
531 - (void)setHeaderToolTip:(CPString)aToolTip
532 {
533  _headerToolTip = aToolTip;
534 }
535 
539 - (CPString)headerToolTip
540 {
541  return _headerToolTip;
542 }
543 
547 - (void)_postDidResizeNotificationWithOldWidth:(float)oldWidth
548 {
549  [[self tableView] _didResizeTableColumn:self];
550 
552  postNotificationName:CPTableViewColumnDidResizeNotification
553  object:[self tableView]
554  userInfo:[CPDictionary dictionaryWithObjects:[self, oldWidth] forKeys:[@"CPTableColumn", "CPOldWidth"]]];
555 }
556 
557 @end
559 {
560  id __doxygen__;
561 }
562 
563 - (void)setValueFor:(CPString)aBinding
564 {
565  var tableView = [_source tableView],
566  column = [[tableView tableColumns] indexOfObjectIdenticalTo:_source],
567  rowIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [tableView numberOfRows])],
568  columnIndexes = [CPIndexSet indexSetWithIndex:column];
569 
570  [tableView reloadDataForRowIndexes:rowIndexes columnIndexes:columnIndexes];
571 }
572 
573 - (CPSortDescriptor)_defaultSortDescriptorPrototype
574 {
575  if (![self createsSortDescriptor])
576  return nil;
577 
578  var keyPath = [_info objectForKey:CPObservedKeyPathKey],
579  dotIndex = keyPath.indexOf(".");
580 
581  if (dotIndex === CPNotFound)
582  return nil;
583 
584  var firstPart = keyPath.substring(0, dotIndex),
585  key = keyPath.substring(dotIndex + 1);
586 
588 }
589 
590 - (BOOL)createsSortDescriptor
591 {
592  var options = [_info objectForKey:CPOptionsKey],
593  optionValue = [options objectForKey:CPCreatesSortDescriptorBindingOption];
594  return optionValue === nil ? YES : [optionValue boolValue];
595 }
596 
597 @end
598 
600 
601 + (id)_binderClassForBinding:(CPString)aBinding
602 {
603  if (aBinding == CPValueBinding)
605 
606  return [super _binderClassForBinding:aBinding];
607 }
608 
617 - (void)bind:(CPString)aBinding toObject:(id)anObject withKeyPath:(CPString)aKeyPath options:(CPDictionary)options
618 {
619  [super bind:aBinding toObject:anObject withKeyPath:aKeyPath options:options];
620 
621  if (![aBinding isEqual:@"someListOfExceptedBindings(notAcceptedBindings)"])
622  {
623  // Bind the table to the array controller this column is bound to.
624  // Note that anObject might not be the array controller. E.g. the keypath could be something like
625  // somePathTo.anArrayController.arrangedObjects.aKey. Cocoa doesn't support this but it is consistent
626  // and it makes sense.
627  var acIndex = aKeyPath.lastIndexOf("arrangedObjects."),
628  arrayController = anObject;
629 
630  if (acIndex > 1)
631  {
632  var firstPart = aKeyPath.substring(0, acIndex - 1);
633  arrayController = [anObject valueForKeyPath:firstPart];
634  }
635 
636  [[self tableView] _establishBindingsIfUnbound:arrayController];
637  }
638 }
639 
643 - (void)_prepareDataView:(CPView)aDataView forRow:(unsigned)aRow
644 {
645  var bindingsDictionary = [CPBinder allBindingsForObject:self],
646  keys = [bindingsDictionary allKeys];
647 
648  for (var i = 0, count = [keys count]; i < count; i++)
649  {
650  var bindingName = keys[i],
651  bindingPath = [aDataView _replacementKeyPathForBinding:bindingName],
652  binding = [bindingsDictionary objectForKey:bindingName],
653  bindingInfo = binding._info,
654  destination = [bindingInfo objectForKey:CPObservedObjectKey],
655  keyPath = [bindingInfo objectForKey:CPObservedKeyPathKey],
656  dotIndex = keyPath.lastIndexOf("."),
657  value;
658 
659  if (dotIndex === CPNotFound)
660  value = [[destination valueForKeyPath:keyPath] objectAtIndex:aRow];
661  else
662  {
663  /*
664  Optimize the prototypical use case where the key path describes a value
665  in an array. Without this optimization, we call CPArray's valueForKey
666  which generates as many values as objects in the array, of which we then
667  pick one and throw away the rest.
668 
669  The optimization is to get the array and access the value directly. This
670  turns the operation into a single access regardless of how long the model
671  array is.
672  */
673 
674  var firstPart = keyPath.substring(0, dotIndex),
675  secondPart = keyPath.substring(dotIndex + 1),
676  firstValue = [destination valueForKeyPath:firstPart];
677 
678  if ([firstValue isKindOfClass:CPArray])
679  value = [[firstValue objectAtIndex:aRow] valueForKeyPath:secondPart];
680  else
681  value = [[firstValue valueForKeyPath:secondPart] objectAtIndex:aRow];
682  }
683 
684  value = [binding transformValue:value withOptions:[bindingInfo objectForKey:CPOptionsKey]];
685  [aDataView setValue:value forKey:@"objectValue"];
686  }
687 }
688 
692 - (void)_reverseSetDataView:(CPView)aDataView forRow:(unsigned)aRow
693 {
694  var bindingsDictionary = [CPBinder allBindingsForObject:self],
695  keys = [bindingsDictionary allKeys],
696  newValue = [aDataView valueForKey:@"objectValue"];
697 
698  for (var i = 0, count = [keys count]; i < count; i++)
699  {
700  var bindingName = keys[i],
701  bindingPath = [aDataView _replacementKeyPathForBinding:bindingName],
702  binding = [bindingsDictionary objectForKey:bindingName],
703  bindingInfo = binding._info,
704  destination = [bindingInfo objectForKey:CPObservedObjectKey],
705  keyPath = [bindingInfo objectForKey:CPObservedKeyPathKey],
706  options = [bindingInfo objectForKey:CPOptionsKey],
707  dotIndex = keyPath.lastIndexOf(".");
708 
709  newValue = [binding reverseTransformValue:newValue withOptions:options];
710 
711  if (dotIndex === CPNotFound)
712  [[destination valueForKeyPath:keyPath] replaceObjectAtIndex:aRow withObject:newValue];
713  else
714  {
715  var firstPart = keyPath.substring(0, dotIndex),
716  secondPart = keyPath.substring(dotIndex + 1),
717  firstValue = [destination valueForKeyPath:firstPart];
718 
719  if ([firstValue isKindOfClass:CPArray])
720  [[firstValue objectAtIndex:aRow] setValue:newValue forKeyPath:secondPart];
721  else
722  [[firstValue valueForKeyPath:secondPart] replaceObjectAtIndex:aRow withObject:newValue];
723  }
724  }
725 }
726 
727 @end
728 
729 var CPTableColumnIdentifierKey = @"CPTableColumnIdentifierKey",
730  CPTableColumnHeaderViewKey = @"CPTableColumnHeaderViewKey",
731  CPTableColumnDataViewKey = @"CPTableColumnDataViewKey",
732  CPTableColumnWidthKey = @"CPTableColumnWidthKey",
733  CPTableColumnMinWidthKey = @"CPTableColumnMinWidthKey",
734  CPTableColumnMaxWidthKey = @"CPTableColumnMaxWidthKey",
735  CPTableColumnResizingMaskKey = @"CPTableColumnResizingMaskKey",
736  CPTableColumnIsHiddenKey = @"CPTableColumnIsHiddenKey",
737  CPSortDescriptorPrototypeKey = @"CPSortDescriptorPrototypeKey",
738  CPTableColumnIsEditableKey = @"CPTableColumnIsEditableKey";
739 
741 
745 - (id)initWithCoder:(CPCoder)aCoder
746 {
747  self = [super init];
748 
749  if (self)
750  {
751  _dataViewData = { };
752 
753  _width = [aCoder decodeFloatForKey:CPTableColumnWidthKey];
754  _minWidth = [aCoder decodeFloatForKey:CPTableColumnMinWidthKey];
755  _maxWidth = [aCoder decodeFloatForKey:CPTableColumnMaxWidthKey];
756 
757  [self setIdentifier:[aCoder decodeObjectForKey:CPTableColumnIdentifierKey]];
758  [self setHeaderView:[aCoder decodeObjectForKey:CPTableColumnHeaderViewKey]];
759  [self setDataView:[aCoder decodeObjectForKey:CPTableColumnDataViewKey]];
760 
761  _resizingMask = [aCoder decodeIntForKey:CPTableColumnResizingMaskKey];
762  _isHidden = [aCoder decodeBoolForKey:CPTableColumnIsHiddenKey];
763  _isEditable = [aCoder decodeBoolForKey:CPTableColumnIsEditableKey];
764 
765  _sortDescriptorPrototype = [aCoder decodeObjectForKey:CPSortDescriptorPrototypeKey];
766  }
767 
768  return self;
769 }
770 
774 - (void)encodeWithCoder:(CPCoder)aCoder
775 {
776  [aCoder encodeObject:_identifier forKey:CPTableColumnIdentifierKey];
777 
778  [aCoder encodeFloat:_width forKey:CPTableColumnWidthKey];
779  [aCoder encodeFloat:_minWidth forKey:CPTableColumnMinWidthKey];
780  [aCoder encodeFloat:_maxWidth forKey:CPTableColumnMaxWidthKey];
781 
782  [aCoder encodeObject:_headerView forKey:CPTableColumnHeaderViewKey];
783  [aCoder encodeObject:_dataView forKey:CPTableColumnDataViewKey];
784 
785  [aCoder encodeObject:_resizingMask forKey:CPTableColumnResizingMaskKey];
786  [aCoder encodeBool:_isHidden forKey:CPTableColumnIsHiddenKey];
787  [aCoder encodeBool:_isEditable forKey:CPTableColumnIsEditableKey];
788 
789  [aCoder encodeObject:_sortDescriptorPrototype forKey:CPSortDescriptorPrototypeKey];
790 }
791 
792 @end
793 
798 - (void)setHeaderCell:(CPView)aView
799 {
800  [CPException raise:CPUnsupportedMethodException
801  reason:@"setHeaderCell: is not supported. Use -setHeaderView:aView instead."];
802 }
803 
807 - (CPView)headerCell
808 {
809  [CPException raise:CPUnsupportedMethodException
810  reason:@"headCell is not supported. Use -headerView instead."];
811 }
812 
816 - (void)setDataCell:(CPView)aView
817 {
818  [CPException raise:CPUnsupportedMethodException
819  reason:@"setDataCell: is not supported. Use -setDataView:aView instead."];
820 }
821 
825 - (CPView)dataCell
826 {
827  [CPException raise:CPUnsupportedMethodException
828  reason:@"dataCell is not supported. Use -dataView instead."];
829 }
830 
834 - (id)dataCellForRow:(int)row
835 {
836  [CPException raise:CPUnsupportedMethodException
837  reason:@"dataCellForRow: is not supported. Use -dataViewForRow:row instead."];
838 }
839 
840 @end
841 
843 
847 - (BOOL)disableResizingPosting
848 {
849  return _disableResizingPosting;
850 }
851 
855 - (void)setDisableResizingPosting:(BOOL)aValue
856 {
857  _disableResizingPosting = aValue;
858 }
859 
860 @end