API  0.9.10
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 
535 {
536  return [CPString stringWithFormat:"<%@ 0x%@ identifier=%@>", [self className], [CPString stringWithHash:[self UID]], [self identifier]];
537 }
538 
539 @end
541 {
542  id __doxygen__;
543 }
544 
545 - (void)setValueFor:(CPString)aBinding
546 {
547  var tableView = [_source tableView],
548  newNumberOfRows = [tableView _numberOfRows];
549 
550  if ([tableView numberOfRows] == newNumberOfRows)
551  {
552  var rowIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, newNumberOfRows)],
553  column = [[tableView tableColumns] indexOfObjectIdenticalTo:_source],
554  columnIndexes = [CPIndexSet indexSetWithIndex:column];
555 
556  // Reloads objectValues only, not the views.
557  // FIXME: reload data for all rows or just rows intersecting exposed rows ?
558  [tableView _reloadDataForRowIndexes:rowIndexes columnIndexes:columnIndexes];
559  }
560  else
561  {
562  [tableView reloadData];
563  }
564 }
565 
566 - (CPSortDescriptor)_defaultSortDescriptorPrototype
567 {
568  if (![self createsSortDescriptor])
569  return nil;
570 
571  var keyPath = [_info objectForKey:CPObservedKeyPathKey],
572  dotIndex = keyPath.indexOf(".");
573 
574  if (dotIndex === CPNotFound)
575  return nil;
576 
577  var firstPart = keyPath.substring(0, dotIndex),
578  key = keyPath.substring(dotIndex + 1);
579 
581 }
582 
583 - (BOOL)createsSortDescriptor
584 {
585  var options = [_info objectForKey:CPOptionsKey],
586  optionValue = [options objectForKey:CPCreatesSortDescriptorBindingOption];
587  return optionValue === nil ? YES : [optionValue boolValue];
588 }
589 
590 @end
591 
593 
594 + (Class)_binderClassForBinding:(CPString)aBinding
595 {
596  if (aBinding == CPValueBinding)
598 
599  return [super _binderClassForBinding:aBinding];
600 }
601 
610 - (void)bind:(CPString)aBinding toObject:(id)anObject withKeyPath:(CPString)aKeyPath options:(CPDictionary)options
611 {
612  [super bind:aBinding toObject:anObject withKeyPath:aKeyPath options:options];
613 
614  if (![aBinding isEqual:@"someListOfExceptedBindings(notAcceptedBindings)"])
615  {
616  // Bind the table to the array controller this column is bound to.
617  // Note that anObject might not be the array controller. E.g. the keypath could be something like
618  // somePathTo.anArrayController.arrangedObjects.aKey. Cocoa doesn't support this but it is consistent
619  // and it makes sense.
620  var acIndex = aKeyPath.lastIndexOf("arrangedObjects."),
621  arrayController = anObject;
622 
623  if (acIndex > 1)
624  {
625  var firstPart = aKeyPath.substring(0, acIndex - 1);
626  arrayController = [anObject valueForKeyPath:firstPart];
627  }
628 
629  [[self tableView] _establishBindingsIfUnbound:arrayController];
630  }
631 }
632 
636 - (void)_prepareDataView:(CPView)aDataView forRow:(unsigned)aRow
637 {
638  var bindingsDictionary = [CPBinder allBindingsForObject:self],
639  keys = [bindingsDictionary allKeys];
640 
641  for (var i = 0, count = [keys count]; i < count; i++)
642  {
643  var bindingName = keys[i],
644  bindingPath = [aDataView _replacementKeyPathForBinding:bindingName],
645  binding = [bindingsDictionary objectForKey:bindingName],
646  bindingInfo = binding._info,
647  destination = [bindingInfo objectForKey:CPObservedObjectKey],
648  keyPath = [bindingInfo objectForKey:CPObservedKeyPathKey],
649  dotIndex = keyPath.lastIndexOf("."),
650  value;
651 
652  if (dotIndex === CPNotFound)
653  value = [[destination valueForKeyPath:keyPath] objectAtIndex:aRow];
654  else
655  {
656  /*
657  Optimize the prototypical use case where the key path describes a value
658  in an array. Without this optimization, we call CPArray's valueForKey
659  which generates as many values as objects in the array, of which we then
660  pick one and throw away the rest.
661 
662  The optimization is to get the array and access the value directly. This
663  turns the operation into a single access regardless of how long the model
664  array is.
665  */
666 
667  var firstPart = keyPath.substring(0, dotIndex),
668  secondPart = keyPath.substring(dotIndex + 1),
669  firstValue = [destination valueForKeyPath:firstPart];
670 
671  if ([firstValue isKindOfClass:CPArray])
672  value = [[firstValue objectAtIndex:aRow] valueForKeyPath:secondPart];
673  else
674  value = [[firstValue valueForKeyPath:secondPart] objectAtIndex:aRow];
675  }
676 
677  value = [binding transformValue:value withOptions:[bindingInfo objectForKey:CPOptionsKey]];
678  [aDataView setValue:value forKey:@"objectValue"];
679  }
680 }
681 
685 - (void)_reverseSetDataView:(CPView)aDataView forRow:(unsigned)aRow
686 {
687  var bindingsDictionary = [CPBinder allBindingsForObject:self],
688  keys = [bindingsDictionary allKeys],
689  newValue = [aDataView valueForKey:@"objectValue"];
690 
691  for (var i = 0, count = [keys count]; i < count; i++)
692  {
693  var bindingName = keys[i],
694  bindingPath = [aDataView _replacementKeyPathForBinding:bindingName],
695  binding = [bindingsDictionary objectForKey:bindingName],
696  bindingInfo = binding._info,
697  destination = [bindingInfo objectForKey:CPObservedObjectKey],
698  keyPath = [bindingInfo objectForKey:CPObservedKeyPathKey],
699  options = [bindingInfo objectForKey:CPOptionsKey],
700  dotIndex = keyPath.lastIndexOf(".");
701 
702  newValue = [binding reverseTransformValue:newValue withOptions:options];
703 
704  if (dotIndex === CPNotFound)
705  [[destination valueForKeyPath:keyPath] replaceObjectAtIndex:aRow withObject:newValue];
706  else
707  {
708  var firstPart = keyPath.substring(0, dotIndex),
709  secondPart = keyPath.substring(dotIndex + 1),
710  firstValue = [destination valueForKeyPath:firstPart];
711 
712  if ([firstValue isKindOfClass:CPArray])
713  [[firstValue objectAtIndex:aRow] setValue:newValue forKeyPath:secondPart];
714  else
715  [[firstValue valueForKeyPath:secondPart] replaceObjectAtIndex:aRow withObject:newValue];
716  }
717  }
718 }
719 
720 @end
721 
722 var CPTableColumnIdentifierKey = @"CPTableColumnIdentifierKey",
723  CPTableColumnHeaderViewKey = @"CPTableColumnHeaderViewKey",
724  CPTableColumnDataViewKey = @"CPTableColumnDataViewKey",
725  CPTableColumnWidthKey = @"CPTableColumnWidthKey",
726  CPTableColumnMinWidthKey = @"CPTableColumnMinWidthKey",
727  CPTableColumnMaxWidthKey = @"CPTableColumnMaxWidthKey",
728  CPTableColumnResizingMaskKey = @"CPTableColumnResizingMaskKey",
729  CPTableColumnIsHiddenKey = @"CPTableColumnIsHiddenKey",
730  CPSortDescriptorPrototypeKey = @"CPSortDescriptorPrototypeKey",
731  CPTableColumnIsEditableKey = @"CPTableColumnIsEditableKey";
732 
734 
738 - (id)initWithCoder:(CPCoder)aCoder
739 {
740  self = [super init];
741 
742  if (self)
743  {
744  _dataViewData = nil;
745 
746  _width = [aCoder decodeFloatForKey:CPTableColumnWidthKey];
747  _minWidth = [aCoder decodeFloatForKey:CPTableColumnMinWidthKey];
748  _maxWidth = [aCoder decodeFloatForKey:CPTableColumnMaxWidthKey];
749 
750  [self setIdentifier:[aCoder decodeObjectForKey:CPTableColumnIdentifierKey]];
751  [self setHeaderView:[aCoder decodeObjectForKey:CPTableColumnHeaderViewKey]];
752  [self setDataView:[aCoder decodeObjectForKey:CPTableColumnDataViewKey]];
753 
754  _resizingMask = [aCoder decodeIntForKey:CPTableColumnResizingMaskKey];
755  _isHidden = [aCoder decodeBoolForKey:CPTableColumnIsHiddenKey];
756  _isEditable = [aCoder decodeBoolForKey:CPTableColumnIsEditableKey];
757 
758  _sortDescriptorPrototype = [aCoder decodeObjectForKey:CPSortDescriptorPrototypeKey];
759  }
760 
761  return self;
762 }
763 
767 - (void)encodeWithCoder:(CPCoder)aCoder
768 {
769  [aCoder encodeObject:_identifier forKey:CPTableColumnIdentifierKey];
770 
771  [aCoder encodeFloat:_width forKey:CPTableColumnWidthKey];
772  [aCoder encodeFloat:_minWidth forKey:CPTableColumnMinWidthKey];
773  [aCoder encodeFloat:_maxWidth forKey:CPTableColumnMaxWidthKey];
774 
775  [aCoder encodeObject:_headerView forKey:CPTableColumnHeaderViewKey];
776  [aCoder encodeObject:_dataView forKey:CPTableColumnDataViewKey];
777 
778  [aCoder encodeObject:_resizingMask forKey:CPTableColumnResizingMaskKey];
779  [aCoder encodeBool:_isHidden forKey:CPTableColumnIsHiddenKey];
780  [aCoder encodeBool:_isEditable forKey:CPTableColumnIsEditableKey];
781 
782  [aCoder encodeObject:_sortDescriptorPrototype forKey:CPSortDescriptorPrototypeKey];
783 }
784 
785 @end
786 
791 - (void)setHeaderCell:(CPView)aView
792 {
793  [CPException raise:CPUnsupportedMethodException
794  reason:@"setHeaderCell: is not supported. Use -setHeaderView:aView instead."];
795 }
796 
801 {
802  [CPException raise:CPUnsupportedMethodException
803  reason:@"headCell is not supported. Use -headerView instead."];
804 }
805 
809 - (void)setDataCell:(CPView)aView
810 {
811  [CPException raise:CPUnsupportedMethodException
812  reason:@"setDataCell: is not supported. Use -setDataView:aView instead."];
813 }
814 
819 {
820  [CPException raise:CPUnsupportedMethodException
821  reason:@"dataCell is not supported. Use -dataView instead."];
822 }
823 
827 - (id)dataCellForRow:(CPInteger)row
828 {
829  [CPException raise:CPUnsupportedMethodException
830  reason:@"dataCellForRow: is not supported. Use -dataViewForRow:row instead."];
831 }
832 
833 @end
834 
836 
841 {
842  return _disableResizingPosting;
843 }
844 
848 - (void)setDisableResizingPosting:(BOOL)aValue
849 {
850  _disableResizingPosting = aValue;
851 }
852 
853 @end
Used to implement exception handling (creating & raising).
Definition: CPException.h:2
CPString description()
void bind:toObject:withKeyPath:options:(CPString aBinding, [toObject] id anObject, [withKeyPath] CPString aKeyPath, [options] CPDictionary options)
BOOL setThemeState:(ThemeState aState)
Definition: CPView.j:3214
CGRect frame
void setMinWidth:(float aMinWidth)
var isEqual
CPTableColumnUserResizingMask
Definition: CPTableColumn.j:28
void setHeaderView:(CPView aView)
var CPSortDescriptorPrototypeKey
CPString className()
Definition: CPObject.j:527
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()
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:1579
var CPTableColumnMinWidthKey
CPTableColumnAutoresizingMask
Definition: CPTableColumn.j:27
var CPTableColumnDataViewKey
id stringWithHash:(unsigned aHash)
Definition: CPString.j:107
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
CPString UID()
Definition: CPObject.j:552
void setDataView:(CPView aView)
Definition: CPView.j:136
CPData archivedDataWithRootObject:(id anObject)
id stringWithFormat:(CPString format, [,] ...)
Definition: CPString.j:166