API  0.9.9
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 
40 @implementation CPTableColumn : CPObject
41 {
42  CPTableView _tableView;
43  CPView _headerView;
44  CPView _dataView;
45  CPData _dataViewData;
46 
47  float _width;
48  float _minWidth;
49  float _maxWidth;
50  unsigned _resizingMask;
51 
52  id _identifier;
53  BOOL _isEditable;
54  CPSortDescriptor _sortDescriptorPrototype;
55  BOOL _isHidden;
56  CPString _headerToolTip;
57 
58  BOOL _disableResizingPosting;
59 }
60 
64 - (id)init
65 {
66  return [self initWithIdentifier:@""];
67 }
68 
73 - (id)initWithIdentifier:(id)anIdentifier
74 {
75  self = [super init];
76 
77  if (self)
78  {
79  _dataViewData = nil;
80 
81  _width = 100.0;
82  _minWidth = 10.0;
83  _maxWidth = 1000000.0;
85  _disableResizingPosting = NO;
86 
87  [self setIdentifier:anIdentifier];
88 
89  var header = [[_CPTableColumnHeaderView alloc] initWithFrame:CGRectMakeZero()];
90  [self setHeaderView:header];
91 
92  [self setDataView:[CPTextField new]];
93  }
94 
95  return self;
96 }
97 
101 - (void)setTableView:(CPTableView)aTableView
102 {
103  _tableView = aTableView;
104 }
105 
110 {
111  return _tableView;
112 }
113 
124 - (int)_tryToResizeToWidth:(int)width
125 {
126  var min = [self minWidth],
127  max = [self maxWidth],
128  newWidth = ROUND(MIN(MAX(width, min), max));
129 
130  [self setWidth:newWidth];
131 
132  return newWidth - width;
133 }
134 
142 - (void)setWidth:(float)aWidth
143 {
144  aWidth = +aWidth;
145 
146  if (_width === aWidth)
147  return;
148 
149  var newWidth = MIN(MAX(aWidth, [self minWidth]), [self maxWidth]);
150 
151  if (_width === newWidth)
152  return;
153 
154  var oldWidth = _width;
155 
156  _width = newWidth;
157 
158  var tableView = [self tableView];
159 
160  if (tableView)
161  {
162  var index = [[tableView tableColumns] indexOfObjectIdenticalTo:self],
163  dirtyTableColumnRangeIndex = tableView._dirtyTableColumnRangeIndex;
164 
165  if (dirtyTableColumnRangeIndex < 0)
166  tableView._dirtyTableColumnRangeIndex = index;
167  else
168  tableView._dirtyTableColumnRangeIndex = MIN(index, tableView._dirtyTableColumnRangeIndex);
169 
170  var rows = tableView._exposedRows,
171  columns = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(index, [tableView._exposedColumns lastIndex] - index + 1)];
172 
173  // 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.
174  [tableView _layoutViewsForRowIndexes:rows columnIndexes:columns];
175  [tableView tile];
176 
177  if (!_disableResizingPosting)
178  [[self tableView] _didResizeTableColumn:self oldWidth:oldWidth];
179  }
180 }
181 
185 - (float)width
186 {
187  return _width;
188 }
189 
194 - (void)setMinWidth:(float)aMinWidth
195 {
196  aMinWidth = +aMinWidth;
197 
198  if (_minWidth === aMinWidth)
199  return;
200 
201  _minWidth = aMinWidth;
202 
203  var width = [self width],
204  newWidth = MAX(width, [self minWidth]);
205 
206  if (width !== newWidth)
207  [self setWidth:newWidth];
208 }
209 
213 - (float)minWidth
214 {
215  return _minWidth;
216 }
217 
222 - (void)setMaxWidth:(float)aMaxWidth
223 {
224  aMaxWidth = +aMaxWidth;
225 
226  if (_maxWidth === aMaxWidth)
227  return;
228 
229  _maxWidth = aMaxWidth;
230 
231  var width = [self width],
232  newWidth = MIN(width, [self maxWidth]);
233 
234  if (width !== newWidth)
235  [self setWidth:newWidth];
236 }
237 
241 - (float)maxWidth
242 {
243  return _maxWidth;
244 }
245 
257 - (void)setResizingMask:(unsigned)aResizingMask
258 {
259  _resizingMask = aResizingMask;
260 }
261 
262 
266 - (unsigned)resizingMask
267 {
268  return _resizingMask;
269 }
270 
274 - (void)sizeToFit
275 {
276  var width = CGRectGetWidth([_headerView frame]);
277 
278  if (width < [self minWidth])
279  [self setMinWidth:width];
280  else if (width > [self maxWidth])
281  [self setMaxWidth:width]
282 
283  if (_width !== width)
284  [self setWidth:width];
285 }
286 
287 
297 - (void)setHeaderView:(CPView)aView
298 {
299  if (!aView)
300  [CPException raise:CPInvalidArgumentException reason:@"Attempt to set nil header view on " + [self description]];
301 
302  _headerView = aView;
303 
304  var tableHeaderView = [_tableView headerView];
305 
306  [tableHeaderView setNeedsLayout];
307  [tableHeaderView setNeedsDisplay:YES];
308 }
309 
317 {
318  return _headerView;
319 }
320 
385 - (void)setDataView:(CPView)aView
386 {
387  if (_dataView === aView)
388  return;
389 
390  [aView setThemeState:CPThemeStateTableDataView];
391 
392  _dataView = aView;
393  _dataViewData = [CPKeyedArchiver archivedDataWithRootObject:aView];
394 }
395 
397 {
398  return _dataView;
399 }
400 
401 /*
402  Returns the CPView object used by the CPTableView to draw values for the receiver.
403 
404  By default, this method just calls dataView. Subclassers can override if they need to
405  potentially use different "cells" or dataViews for different rows. Subclasses should expect this method
406  to be invoked with row equal to -1 in cases where no actual row is involved but the table
407  view needs to get some generic cell info.
408 */
409 - (id)dataViewForRow:(CPInteger)aRowIndex
410 {
411  return [self dataView];
412 }
413 
417 - (id)_newDataView
418 {
419  if (!_dataViewData)
420  return nil;
421 
422  var newDataView = [CPKeyedUnarchiver unarchiveObjectWithData:_dataViewData];
423  [newDataView setAutoresizingMask:CPViewNotSizable];
424 
425  return newDataView;
426 }
427 
428 //Setting the Identifier
429 
433 - (void)setIdentifier:(id)anIdentifier
434 {
435  _identifier = anIdentifier;
436 }
437 
442 {
443  return _identifier;
444 }
445 
446 //Controlling Editability
447 
451 - (void)setEditable:(BOOL)shouldBeEditable
452 {
453  _isEditable = shouldBeEditable;
454 }
455 
460 - (BOOL)isEditable
461 {
462  return _isEditable;
463 }
464 
468 - (void)setSortDescriptorPrototype:(CPSortDescriptor)aSortDescriptor
469 {
470  _sortDescriptorPrototype = aSortDescriptor;
471 }
472 
477 {
478  if (_sortDescriptorPrototype)
479  return _sortDescriptorPrototype;
480 
481  var binderClass = [[self class] _binderClassForBinding:CPValueBinding],
482  binding = [binderClass getBinding:CPValueBinding forObject:self];
483 
484  return [binding _defaultSortDescriptorPrototype];
485 }
486 
491 - (void)setHidden:(BOOL)shouldBeHidden
492 {
493  shouldBeHidden = !!shouldBeHidden
494 
495  if (_isHidden === shouldBeHidden)
496  return;
497 
498  _isHidden = shouldBeHidden;
499 
500  [[self headerView] setHidden:shouldBeHidden];
501  [[self tableView] _tableColumnVisibilityDidChange:self];
502 }
503 
507 - (BOOL)isHidden
508 {
509  return _isHidden;
510 }
511 
512 //Setting Tool Tips
513 
518 - (void)setHeaderToolTip:(CPString)aToolTip
519 {
520  _headerToolTip = aToolTip;
521 }
522 
527 {
528  return _headerToolTip;
529 }
530 
531 @end
533 {
534  id __doxygen__;
535 }
536 
537 - (void)setValueFor:(CPString)aBinding
538 {
539  var tableView = [_source tableView],
540  newNumberOfRows = [tableView _numberOfRows];
541 
542  if ([tableView numberOfRows] == newNumberOfRows)
543  {
544  var rowIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, newNumberOfRows)],
545  column = [[tableView tableColumns] indexOfObjectIdenticalTo:_source],
546  columnIndexes = [CPIndexSet indexSetWithIndex:column];
547 
548  // Reloads objectValues only, not the views.
549  // FIXME: reload data for all rows or just rows intersecting exposed rows ?
550  [tableView _reloadDataForRowIndexes:rowIndexes columnIndexes:columnIndexes];
551  }
552  else
553  {
554  [tableView reloadData];
555  }
556 }
557 
558 - (CPSortDescriptor)_defaultSortDescriptorPrototype
559 {
560  if (![self createsSortDescriptor])
561  return nil;
562 
563  var keyPath = [_info objectForKey:CPObservedKeyPathKey],
564  dotIndex = keyPath.indexOf(".");
565 
566  if (dotIndex === CPNotFound)
567  return nil;
568 
569  var firstPart = keyPath.substring(0, dotIndex),
570  key = keyPath.substring(dotIndex + 1);
571 
573 }
574 
575 - (BOOL)createsSortDescriptor
576 {
577  var options = [_info objectForKey:CPOptionsKey],
578  optionValue = [options objectForKey:CPCreatesSortDescriptorBindingOption];
579  return optionValue === nil ? YES : [optionValue boolValue];
580 }
581 
582 @end
583 
585 
586 + (Class)_binderClassForBinding:(CPString)aBinding
587 {
588  if (aBinding == CPValueBinding)
590 
591  return [super _binderClassForBinding:aBinding];
592 }
593 
602 - (void)bind:(CPString)aBinding toObject:(id)anObject withKeyPath:(CPString)aKeyPath options:(CPDictionary)options
603 {
604  [super bind:aBinding toObject:anObject withKeyPath:aKeyPath options:options];
605 
606  if (![aBinding isEqual:@"someListOfExceptedBindings(notAcceptedBindings)"])
607  {
608  // Bind the table to the array controller this column is bound to.
609  // Note that anObject might not be the array controller. E.g. the keypath could be something like
610  // somePathTo.anArrayController.arrangedObjects.aKey. Cocoa doesn't support this but it is consistent
611  // and it makes sense.
612  var acIndex = aKeyPath.lastIndexOf("arrangedObjects."),
613  arrayController = anObject;
614 
615  if (acIndex > 1)
616  {
617  var firstPart = aKeyPath.substring(0, acIndex - 1);
618  arrayController = [anObject valueForKeyPath:firstPart];
619  }
620 
621  [[self tableView] _establishBindingsIfUnbound:arrayController];
622  }
623 }
624 
628 - (void)_prepareDataView:(CPView)aDataView forRow:(unsigned)aRow
629 {
630  var bindingsDictionary = [CPBinder allBindingsForObject:self],
631  keys = [bindingsDictionary allKeys];
632 
633  for (var i = 0, count = [keys count]; i < count; i++)
634  {
635  var bindingName = keys[i],
636  bindingPath = [aDataView _replacementKeyPathForBinding:bindingName],
637  binding = [bindingsDictionary objectForKey:bindingName],
638  bindingInfo = binding._info,
639  destination = [bindingInfo objectForKey:CPObservedObjectKey],
640  keyPath = [bindingInfo objectForKey:CPObservedKeyPathKey],
641  dotIndex = keyPath.lastIndexOf("."),
642  value;
643 
644  if (dotIndex === CPNotFound)
645  value = [[destination valueForKeyPath:keyPath] objectAtIndex:aRow];
646  else
647  {
648  /*
649  Optimize the prototypical use case where the key path describes a value
650  in an array. Without this optimization, we call CPArray's valueForKey
651  which generates as many values as objects in the array, of which we then
652  pick one and throw away the rest.
653 
654  The optimization is to get the array and access the value directly. This
655  turns the operation into a single access regardless of how long the model
656  array is.
657  */
658 
659  var firstPart = keyPath.substring(0, dotIndex),
660  secondPart = keyPath.substring(dotIndex + 1),
661  firstValue = [destination valueForKeyPath:firstPart];
662 
663  if ([firstValue isKindOfClass:CPArray])
664  value = [[firstValue objectAtIndex:aRow] valueForKeyPath:secondPart];
665  else
666  value = [[firstValue valueForKeyPath:secondPart] objectAtIndex:aRow];
667  }
668 
669  value = [binding transformValue:value withOptions:[bindingInfo objectForKey:CPOptionsKey]];
670  [aDataView setValue:value forKey:@"objectValue"];
671  }
672 }
673 
677 - (void)_reverseSetDataView:(CPView)aDataView forRow:(unsigned)aRow
678 {
679  var bindingsDictionary = [CPBinder allBindingsForObject:self],
680  keys = [bindingsDictionary allKeys],
681  newValue = [aDataView valueForKey:@"objectValue"];
682 
683  for (var i = 0, count = [keys count]; i < count; i++)
684  {
685  var bindingName = keys[i],
686  bindingPath = [aDataView _replacementKeyPathForBinding:bindingName],
687  binding = [bindingsDictionary objectForKey:bindingName],
688  bindingInfo = binding._info,
689  destination = [bindingInfo objectForKey:CPObservedObjectKey],
690  keyPath = [bindingInfo objectForKey:CPObservedKeyPathKey],
691  options = [bindingInfo objectForKey:CPOptionsKey],
692  dotIndex = keyPath.lastIndexOf(".");
693 
694  newValue = [binding reverseTransformValue:newValue withOptions:options];
695 
696  if (dotIndex === CPNotFound)
697  [[destination valueForKeyPath:keyPath] replaceObjectAtIndex:aRow withObject:newValue];
698  else
699  {
700  var firstPart = keyPath.substring(0, dotIndex),
701  secondPart = keyPath.substring(dotIndex + 1),
702  firstValue = [destination valueForKeyPath:firstPart];
703 
704  if ([firstValue isKindOfClass:CPArray])
705  [[firstValue objectAtIndex:aRow] setValue:newValue forKeyPath:secondPart];
706  else
707  [[firstValue valueForKeyPath:secondPart] replaceObjectAtIndex:aRow withObject:newValue];
708  }
709  }
710 }
711 
712 @end
713 
714 var CPTableColumnIdentifierKey = @"CPTableColumnIdentifierKey",
715  CPTableColumnHeaderViewKey = @"CPTableColumnHeaderViewKey",
716  CPTableColumnDataViewKey = @"CPTableColumnDataViewKey",
717  CPTableColumnWidthKey = @"CPTableColumnWidthKey",
718  CPTableColumnMinWidthKey = @"CPTableColumnMinWidthKey",
719  CPTableColumnMaxWidthKey = @"CPTableColumnMaxWidthKey",
720  CPTableColumnResizingMaskKey = @"CPTableColumnResizingMaskKey",
721  CPTableColumnIsHiddenKey = @"CPTableColumnIsHiddenKey",
722  CPSortDescriptorPrototypeKey = @"CPSortDescriptorPrototypeKey",
723  CPTableColumnIsEditableKey = @"CPTableColumnIsEditableKey";
724 
726 
730 - (id)initWithCoder:(CPCoder)aCoder
731 {
732  self = [super init];
733 
734  if (self)
735  {
736  _dataViewData = nil;
737 
738  _width = [aCoder decodeFloatForKey:CPTableColumnWidthKey];
739  _minWidth = [aCoder decodeFloatForKey:CPTableColumnMinWidthKey];
740  _maxWidth = [aCoder decodeFloatForKey:CPTableColumnMaxWidthKey];
741 
742  [self setIdentifier:[aCoder decodeObjectForKey:CPTableColumnIdentifierKey]];
743  [self setHeaderView:[aCoder decodeObjectForKey:CPTableColumnHeaderViewKey]];
744  [self setDataView:[aCoder decodeObjectForKey:CPTableColumnDataViewKey]];
745 
746  _resizingMask = [aCoder decodeIntForKey:CPTableColumnResizingMaskKey];
747  _isHidden = [aCoder decodeBoolForKey:CPTableColumnIsHiddenKey];
748  _isEditable = [aCoder decodeBoolForKey:CPTableColumnIsEditableKey];
749 
750  _sortDescriptorPrototype = [aCoder decodeObjectForKey:CPSortDescriptorPrototypeKey];
751  }
752 
753  return self;
754 }
755 
759 - (void)encodeWithCoder:(CPCoder)aCoder
760 {
761  [aCoder encodeObject:_identifier forKey:CPTableColumnIdentifierKey];
762 
763  [aCoder encodeFloat:_width forKey:CPTableColumnWidthKey];
764  [aCoder encodeFloat:_minWidth forKey:CPTableColumnMinWidthKey];
765  [aCoder encodeFloat:_maxWidth forKey:CPTableColumnMaxWidthKey];
766 
767  [aCoder encodeObject:_headerView forKey:CPTableColumnHeaderViewKey];
768  [aCoder encodeObject:_dataView forKey:CPTableColumnDataViewKey];
769 
770  [aCoder encodeObject:_resizingMask forKey:CPTableColumnResizingMaskKey];
771  [aCoder encodeBool:_isHidden forKey:CPTableColumnIsHiddenKey];
772  [aCoder encodeBool:_isEditable forKey:CPTableColumnIsEditableKey];
773 
774  [aCoder encodeObject:_sortDescriptorPrototype forKey:CPSortDescriptorPrototypeKey];
775 }
776 
777 @end
778 
783 - (void)setHeaderCell:(CPView)aView
784 {
785  [CPException raise:CPUnsupportedMethodException
786  reason:@"setHeaderCell: is not supported. Use -setHeaderView:aView instead."];
787 }
788 
793 {
794  [CPException raise:CPUnsupportedMethodException
795  reason:@"headCell is not supported. Use -headerView instead."];
796 }
797 
801 - (void)setDataCell:(CPView)aView
802 {
803  [CPException raise:CPUnsupportedMethodException
804  reason:@"setDataCell: is not supported. Use -setDataView:aView instead."];
805 }
806 
811 {
812  [CPException raise:CPUnsupportedMethodException
813  reason:@"dataCell is not supported. Use -dataView instead."];
814 }
815 
819 - (id)dataCellForRow:(CPInteger)row
820 {
821  [CPException raise:CPUnsupportedMethodException
822  reason:@"dataCellForRow: is not supported. Use -dataViewForRow:row instead."];
823 }
824 
825 @end
826 
828 
833 {
834  return _disableResizingPosting;
835 }
836 
840 - (void)setDisableResizingPosting:(BOOL)aValue
841 {
842  _disableResizingPosting = aValue;
843 }
844 
845 @end
Used to implement exception handling (creating & raising).
Definition: CPException.h:2
void bind:toObject:withKeyPath:options:(CPString aBinding, [toObject] id anObject, [withKeyPath] CPString aKeyPath, [options] CPDictionary options)
BOOL setThemeState:(ThemeState aState)
Definition: CPView.j:3148
CGRect frame
void setMinWidth:(float aMinWidth)
var isEqual
CPTableColumnUserResizingMask
Definition: CPTableColumn.j:28
void setHeaderView:(CPView aView)
var CPSortDescriptorPrototypeKey
CPView headerView()
CPArray tableColumns()
Definition: CPTableView.j:1244
A Cappuccino wrapper for any data type.
Definition: CPData.h:2
void raise:reason:(CPString aName, [reason] CPString aReason)
Definition: CPException.j:66
A collection of unique integers.
Definition: CPIndexSet.h:2
Unarchives objects created using CPKeyedArchiver.
CPSortDescriptor sortDescriptorPrototype()
CPString description()
Definition: CPObject.j:348
id sortDescriptorWithKey:ascending:(CPString aKey, [ascending] BOOL isAscending)
A mutable key-value pair collection.
Definition: CPDictionary.h:2
id initWithIdentifier:(id anIdentifier)
Definition: CPTableColumn.j:73
var CPTableColumnIdentifierKey
void setIdentifier:(id anIdentifier)
CPTableColumnNoResizing
Definition: CPTableColumn.j:26
CPString headerToolTip()
Implements keyed archiving of object graphs (e.g. for storing data).
An immutable string (collection of characters).
Definition: CPString.h:2
void setWidth:(float aWidth)
var CPTableColumnIsHiddenKey
Holds attributes necessary to describe how to sort a set of objects.
void setHidden:(BOOL aFlag)
Definition: CPView.j:1564
var CPTableColumnMinWidthKey
CPTableColumnAutoresizingMask
Definition: CPTableColumn.j:27
var CPTableColumnDataViewKey
var CPTableColumnIsEditableKey
void setMaxWidth:(float aMaxWidth)
var CPTableColumnMaxWidthKey
Defines methods for use when archiving & restoring (enc/decoding).
Definition: CPCoder.h:2
var CPTableColumnHeaderViewKey
CPTableView tableView()
CPNotFound
Definition: CPObjJRuntime.j:62
CPDictionary allBindingsForObject:(id anObject)
unsigned resizingMask()
id unarchiveObjectWithData:(CPData aData)
id init()
Definition: CPObject.j:145
id indexSetWithIndexesInRange:(CPRange aRange)
Definition: CPIndexSet.j:60
var CPTableColumnWidthKey
Class class()
Definition: CPObject.j:179
CPValueBinding
id indexSetWithIndex:(int anIndex)
Definition: CPIndexSet.j:51
var CPTableColumnResizingMaskKey
void setDataView:(CPView aView)
Definition: CPView.j:131
CPData archivedDataWithRootObject:(id anObject)