41 _CPPopUpList _listDelegate;
42 CPComboBoxDataSource _dataSource;
46 int _numberOfVisibleItems;
48 BOOL _hasVerticalScroller;
50 BOOL _popUpButtonCausedResign;
61 @"popup-button-size": CGSizeMake(21.0, 29.0),
62 @"border-inset": CGInsetMake(3.0, 3.0, 3.0, 3.0),
66 + (Class)_binderClassForBinding:(
CPString)aBinding
68 if (aBinding === CPContentBinding || aBinding === CPContentValuesBinding)
69 return [_CPComboBoxContentBinder class];
71 return [
super _binderClassForBinding:aBinding];
74 - (id)initWithFrame:(CGRect)aFrame
86 _items = [CPArray array];
93 _hasVerticalScroller = YES;
94 _selectedStringValue =
@"";
95 _popUpButtonCausedResign = NO;
97 [
self setTheme:[
CPTheme defaultTheme]];
98 [
self setBordered:YES];
99 [
self setBezeled:YES];
100 [
self setEditable:YES];
101 [
self setThemeState:CPComboBoxStateButtonBordered];
104 #pragma mark Setting Display Attributes
106 - (BOOL)hasVerticalScroller
108 return _hasVerticalScroller;
111 - (void)setHasVerticalScroller:(BOOL)flag
115 if (_hasVerticalScroller === flag)
118 _hasVerticalScroller = flag;
119 [[_listDelegate scrollView] setHasVerticalScroller:flag];
122 - (CGSize)intercellSpacing
124 return [[_listDelegate tableView] intercellSpacing];
127 - (void)setIntercellSpacing:(CGSize)aSize
129 [[_listDelegate tableView] setIntercellSpacing:aSize];
132 - (BOOL)isButtonBordered
134 return [
self hasThemeState:CPComboBoxStateButtonBordered];
137 - (void)setButtonBordered:(BOOL)flag
140 [
self setThemeState:CPComboBoxStateButtonBordered];
142 [
self unsetThemeState:CPComboBoxStateButtonBordered];
147 return [[_listDelegate tableView] rowHeight];
150 - (void)setItemHeight:(
float)itemHeight
152 [[_listDelegate tableView] setRowHeight:itemHeight];
155 [[_listDelegate tableView] reloadData];
158 - (int)numberOfVisibleItems
160 return _numberOfVisibleItems;
163 - (void)setNumberOfVisibleItems:(
int)visibleItems
166 _numberOfVisibleItems = MAX(visibleItems, 1);
169 #pragma mark Setting a Delegate
173 return [
super delegate];
182 - (void)setDelegate:(
id )aDelegate
186 if (aDelegate === delegate)
193 [defaultCenter removeObserver:delegate name:CPComboBoxSelectionIsChangingNotification object:self];
194 [defaultCenter removeObserver:delegate name:CPComboBoxSelectionDidChangeNotification object:self];
195 [defaultCenter removeObserver:delegate name:CPComboBoxWillDismissNotification object:self];
196 [defaultCenter removeObserver:delegate name:CPComboBoxWillPopUpNotification object:self];
201 if ([aDelegate respondsToSelector:
@selector(comboBoxSelectionIsChanging:)])
202 [defaultCenter addObserver:delegate
203 selector:@selector(comboBoxSelectionIsChanging:)
204 name:CPComboBoxSelectionIsChangingNotification
207 if ([aDelegate respondsToSelector:
@selector(comboBoxSelectionDidChange:)])
208 [defaultCenter addObserver:delegate
209 selector:@selector(comboBoxSelectionDidChange:)
210 name:CPComboBoxSelectionDidChangeNotification
213 if ([aDelegate respondsToSelector:
@selector(comboBoxWillPopUp:)])
214 [defaultCenter addObserver:delegate
215 selector:@selector(comboBoxWillPopUp:)
216 name:CPComboBoxWillPopUpNotification
219 if ([aDelegate respondsToSelector:
@selector(comboBoxWillDismiss:)])
220 [defaultCenter addObserver:delegate
221 selector:@selector(comboBoxWillDissmis:)
222 name:CPComboBoxWillDismissNotification
226 [
super setDelegate:aDelegate];
229 #pragma mark Setting a Data Source
233 if (!_usesDataSource)
234 [
self _dataSourceWarningForMethod:_cmd condition:NO];
239 - (void)setDataSource:(
id )aSource
241 if (!_usesDataSource)
242 [
self _dataSourceWarningForMethod:_cmd condition:NO];
243 else if (_dataSource !== aSource)
245 if (![aSource respondsToSelector:
@selector(numberOfItemsInComboBox:)] ||
246 ![aSource respondsToSelector:
@selector(comboBox:objectValueForItemAtIndex:)])
248 CPLog.warn(
"Illegal %s data source (%s). Must implement numberOfItemsInComboBox: and comboBox:objectValueForItemAtIndex:", [
self className], [aSource description]);
251 _dataSource = aSource;
255 - (BOOL)usesDataSource
257 return _usesDataSource;
260 - (void)setUsesDataSource:(BOOL)flag
264 if (_usesDataSource === flag)
267 _usesDataSource = flag;
271 [_items removeAllObjects];
276 #pragma mark Working with an Internal List
278 - (void)addItemsWithObjectValues:(CPArray)objects
280 [_items addObjectsFromArray:objects];
285 - (void)addItemWithObjectValue:(
id)anObject
287 [_items addObject:anObject];
292 - (void)insertItemWithObjectValue:(
id)anObject atIndex:(
int)anIndex
296 [
self _dataSourceWarningForMethod:_cmd condition:YES];
298 [_items insertObject:anObject atIndex:anIndex];
309 - (CPArray)objectValues
312 [
self _dataSourceWarningForMethod:_cmd condition:YES];
317 - (void)removeAllItems
319 [_items removeAllObjects];
324 - (void)removeItemAtIndex:(
int)index
328 [
self _dataSourceWarningForMethod:_cmd condition:YES];
330 [_items removeObjectAtIndex:index];
334 - (void)removeItemWithObjectValue:(
id)anObject
336 [_items removeObject:anObject];
344 return [_dataSource numberOfItemsInComboBox:self];
346 return _items.length;
349 #pragma mark Manipulating the Displayed List
354 - (_CPPopUpList)listDelegate
356 return _listDelegate;
364 - (void)setListDelegate:(_CPPopUpList)aDelegate
366 if (_listDelegate === aDelegate)
373 [defaultCenter removeObserver:self name:_CPPopUpListWillPopUpNotification object:_listDelegate];
374 [defaultCenter removeObserver:self name:_CPPopUpListWillDismissNotification object:_listDelegate];
375 [defaultCenter removeObserver:self name:_CPPopUpListDidDismissNotification object:_listDelegate];
376 [defaultCenter removeObserver:self name:_CPPopUpListItemWasClickedNotification object:_listDelegate];
378 var oldTableView = [_listDelegate tableView];
382 [defaultCenter removeObserver:self name:CPTableViewSelectionIsChangingNotification object:oldTableView];
383 [defaultCenter removeObserver:self name:CPTableViewSelectionDidChangeNotification object:oldTableView];
387 _listDelegate = aDelegate;
389 [defaultCenter addObserver:self
390 selector:@selector(comboBoxWillPopUp:)
391 name:_CPPopUpListWillPopUpNotification
392 object:_listDelegate];
394 [defaultCenter addObserver:self
395 selector:@selector(comboBoxWillDismiss:)
396 name:_CPPopUpListWillDismissNotification
397 object:_listDelegate];
399 [defaultCenter addObserver:self
400 selector:@selector(listDidDismiss:)
401 name:_CPPopUpListDidDismissNotification
402 object:_listDelegate];
404 [defaultCenter addObserver:self
405 selector:@selector(itemWasClicked:)
406 name:_CPPopUpListItemWasClickedNotification
407 object:_listDelegate];
409 [[_listDelegate scrollView] setHasVerticalScroller:_hasVerticalScroller];
411 var tableView = [_listDelegate tableView];
413 [defaultCenter addObserver:self
414 selector:@selector(comboBoxSelectionIsChanging:)
415 name:CPTableViewSelectionIsChangingNotification
418 [defaultCenter addObserver:self
419 selector:@selector(comboBoxSelectionDidChange:)
420 name:CPTableViewSelectionDidChangeNotification
424 [_listDelegate setFont:[
self font]];
425 [_listDelegate setAlignment:[
self alignment]];
428 - (int)indexOfItemWithObjectValue:(
id)anObject
431 [
self _dataSourceWarningForMethod:_cmd condition:YES];
433 return [_items indexOfObject:anObject];
436 - (id)itemObjectValueAtIndex:(
int)index
439 [
self _dataSourceWarningForMethod:_cmd condition:YES];
441 return [_items objectAtIndex:index];
444 - (void)noteNumberOfItemsChanged
446 [[_listDelegate tableView] noteNumberOfRowsChanged];
449 - (void)scrollItemAtIndexToTop:(
int)index
451 [_listDelegate scrollItemAtIndexToTop:index];
454 - (void)scrollItemAtIndexToVisible:(
int)index
456 [[_listDelegate tableView] scrollRowToVisible:index];
461 [[_listDelegate tableView] reloadData];
470 [
self _selectMatchingItem];
476 var inset = [
self currentValueForThemeAttribute:@"border-inset"];
481 [_listDelegate popUpRelativeToRect:[
self _borderFrame] view:self offset:CPComboBoxFocusRingWidth - 1];
485 - (BOOL)listIsVisible
487 return _listDelegate ? [_listDelegate isVisible] : NO;
491 - (void)reloadDataSourceForSelector:(
SEL)cmd
494 [
self _dataSourceWarningForMethod:cmd condition:YES]
504 - (BOOL)takeStringValueFromList
506 if (_usesDataSource && _dataSource && [_dataSource numberOfItemsInComboBox:
self] === 0)
509 var selectedStringValue = [_listDelegate selectedStringValue];
511 if (selectedStringValue === nil)
514 _selectedStringValue = selectedStringValue;
517 [
self _reverseSetBinding];
528 [[
self window] makeFirstResponder:self];
541 #pragma mark Manipulating the Selection
543 - (void)deselectItemAtIndex:(
int)index
545 var table = [_listDelegate tableView],
546 row = [table selectedRow];
551 [table deselectRow:index];
554 - (int)indexOfSelectedItem
556 return [[_listDelegate tableView] selectedRow];
559 - (id)objectValueOfSelectedItem
561 var row = [[_listDelegate tableView] selectedRow];
566 [
self _dataSourceWarningForMethod:_cmd condition:YES];
574 - (void)selectItemAtIndex:(
int)index
576 var table = [_listDelegate tableView],
577 row = [table selectedRow];
585 - (void)selectItemWithObjectValue:(
id)anObject
593 #pragma mark Completing the Text Field
600 - (void)setCompletes:(BOOL)flag
611 var index = [_items indexOfObjectPassingTest:CPComboBoxCompletionTest context:substring];
613 return index !==
CPNotFound ? _items[index] : nil;
621 - (BOOL)forceSelection
623 return _forceSelection;
635 - (void)setForceSelection:(BOOL)flag
637 _forceSelection = !!flag;
640 #pragma mark CPTextField Delegate Methods and Overrides
643 - (BOOL)sendAction:(
SEL)anAction to:(
id)anObject
649 if ([
self listIsVisible])
652 [_listDelegate close];
659 - (void)setObjectValue:(
id)object
667 - (void)interpretKeyEvents:(CPArray)events
669 var theEvent = events[0];
676 if (![theEvent _couldBeKeyEquivalent] && [theEvent characters].charAt(0) !==
CPDeleteCharacter)
678 var value = [
self _inputElement].value,
679 selectedRange = [
self selectedRange];
681 _canComplete =
CPMaxRange(selectedRange) === value.length;
685 [
super interpretKeyEvents:events];
689 - (void)paste:(
id)sender
694 var value = [
self _inputElement].value,
695 selectedRange = [
self selectedRange];
697 _canComplete =
CPMaxRange(selectedRange) === value.length;
702 [
super paste:sender];
714 newString = uncompletedString;
716 if (_completes && _canComplete)
720 if (newString && newString.length > uncompletedString.length)
723 [
self setSelectedRange:CPMakeRange(uncompletedString.length, newString.length - uncompletedString.length)];
727 [
self _selectMatchingItem];
739 if ([[
self window] firstResponder] ===
self)
741 var key = [anEvent charactersIgnoringModifiers];
746 if (![
self listIsVisible])
754 if ([
self listIsVisible])
758 if (_forceSelection && ([
self _inputElement].value !== _selectedStringValue))
764 if ([_listDelegate performKeyEquivalent:anEvent])
768 return [
super performKeyEquivalent:anEvent];
772 - (BOOL)resignFirstResponder
774 var buttonCausedResign = _popUpButtonCausedResign;
776 _popUpButtonCausedResign = NO;
782 var shouldResign = !buttonCausedResign && (!_listDelegate || [_listDelegate controllingViewShouldResign]);
789 var element = [
self _inputElement];
790 window.setTimeout(
function() { element.focus(); }, 0);
797 [_listDelegate close];
805 if (_forceSelection && ![value
isEqual:_selectedStringValue])
809 _selectedStringValue =
@"";
811 return [
super resignFirstResponder];
817 [_listDelegate setFont:aFont];
820 - (void)setAlignment:(CPTextAlignment)alignment
823 [_listDelegate setAlignment:alignment];
826 #pragma mark Pop Up Button Layout
828 - (CGRect)popupButtonRectForBounds:(CGRect)bounds
830 var borderInset = [
self currentValueForThemeAttribute:@"border-inset"],
831 buttonSize = [
self currentValueForThemeAttribute:@"popup-button-size"];
833 bounds.origin.x = CGRectGetMaxX(bounds) - borderInset.right - buttonSize.width;
834 bounds.origin.y += borderInset.top;
836 bounds.
size.width = buttonSize.width;
837 bounds.size.height = buttonSize.height;
842 - (CGRect)rectForEphemeralSubviewNamed:(
CPString)aName
844 if (aName ===
"popup-button-view")
847 return [
super rectForEphemeralSubviewNamed:aName];
852 if (aName ===
"popup-button-view")
854 var view = [[_CPComboBoxPopUpButton alloc] initWithFrame:CGRectMakeZero() comboBox:self];
859 return [
super createEphemeralSubviewNamed:aName];
862 - (void)layoutSubviews
864 [
super layoutSubviews];
866 var popupButtonView = [
self layoutEphemeralSubviewNamed:@"popup-button-view"
867 positioned:CPWindowAbove
868 relativeToEphemeralSubviewNamed:@"content-view"];
871 #pragma mark Internal Helpers
874 - (void)_dataSourceWarningForMethod:(
SEL)cmd condition:(
CPString)flag
876 CPLog.warn(
"-[%s %s] should not be called when usesDataSource is set to %s", [
self className], cmd, flag ?
"YES" :
"NO");
883 - (void)_selectMatchingItem
886 stringValue = [
self stringValue];
890 if (_dataSource && [_dataSource respondsToSelector:
@selector(comboBox:indexOfItemWithStringValue:)])
891 index = [_dataSource comboBox:self indexOfItemWithStringValue:stringValue]
894 index = [
self indexOfItemWithObjectValue:stringValue];
896 [_listDelegate selectRow:index];
901 [_listDelegate scrollItemAtIndexToTop:index];
902 _selectedStringValue = stringValue;
910 - (CGRect)_borderFrame
912 var inset = [
self currentValueForThemeAttribute:@"border-inset"],
913 frame = [
self bounds];
915 frame.origin.x += inset.left;
916 frame.origin.y += inset.top;
917 frame.size.width -= inset.left + inset.right;
918 frame.size.height -= inset.top + inset.bottom;
924 - (void)_popUpButtonWasClicked
926 if (![
self isEnabled])
931 var firstResponder = [[
self window] firstResponder];
933 _popUpButtonCausedResign = firstResponder ===
self;
935 if ([
self listIsVisible])
936 [_listDelegate close];
939 if (firstResponder !==
self)
940 [[
self window] makeFirstResponder:self];
981 if ([_dataSource respondsToSelector:
@selector(comboBox:completedString:)])
982 return [_dataSource comboBox:self completedString:uncompletedString];
989 @implementation CPComboBox (_CPPopUpListDataSource)
991 - (int)numberOfItemsInList:(_CPPopUpList)aList
993 return [
self numberOfItems];
996 - (int)numberOfVisibleItemsInList:(_CPPopUpList)aList
998 return [
self numberOfVisibleItems];
1001 - (id)list:(_CPPopUpList)aList objectValueForItemAtIndex:(
int)index
1003 if (_usesDataSource)
1004 return [_dataSource comboBox:self objectValueForItemAtIndex:index];
1006 return _items[index];
1009 - (id)list:(_CPPopUpList)aList displayValueForObjectValue:(
id)aValue
1011 return aValue ||
@"";
1014 - (
CPString)list:(_CPPopUpList)aList stringValueForObjectValue:(
id)aValue
1016 return String(aValue);
1024 - (void)setContentValues:(CPArray)anArray
1032 - (void)setContent:(CPArray)anArray
1041 [anArray enumerateObjectsUsingBlock:function(object)
1043 values.push([object description]);
1069 [
self _initComboBox];
1071 _items = [aCoder decodeObjectForKey:CPComboBoxItemsKey];
1072 _listDelegate = [aCoder decodeObjectForKey:CPComboBoxListKey];
1073 _delegate = [aCoder decodeObjectForKey:CPComboBoxDelegateKey];
1074 _dataSource = [aCoder decodeObjectForKey:CPComboBoxDataSourceKey];
1075 _usesDataSource = [aCoder decodeBoolForKey:CPComboBoxUsesDataSourceKey];
1076 _completes = [aCoder decodeBoolForKey:CPComboBoxCompletesKey];
1077 _numberOfVisibleItems = [aCoder decodeIntForKey:CPComboBoxNumberOfVisibleItemsKey];
1078 _hasVerticalScroller = [aCoder decodeBoolForKey:CPComboBoxHasVerticalScrollerKey];
1089 [aCoder encodeObject:_items forKey:CPComboBoxItemsKey];
1090 [aCoder encodeObject:_listDelegate forKey:CPComboBoxListKey];
1091 [aCoder encodeObject:_delegate forKey:CPComboBoxDelegateKey];
1092 [aCoder encodeObject:_dataSource forKey:CPComboBoxDataSourceKey];
1093 [aCoder encodeBool:_usesDataSource forKey:CPComboBoxUsesDataSourceKey];
1094 [aCoder encodeBool:_completes forKey:CPComboBoxCompletesKey];
1095 [aCoder encodeInt:_numberOfVisibleItems forKey:CPComboBoxNumberOfVisibleItemsKey];
1096 [aCoder encodeBool:_hasVerticalScroller forKey:CPComboBoxHasVerticalScrollerKey];
1097 [aCoder encodeBool:[
self isButtonBordered] forKey:CPComboBoxButtonBorderedKey];
1105 return object.toString().indexOf(context) === 0;
1112 @implementation _CPComboBoxContentBinder :
CPBinder
1117 - (void)setValueFor:(
CPString)aBinding
1119 var destination = [_info objectForKey:CPObservedObjectKey],
1120 keyPath = [_info objectForKey:CPObservedKeyPathKey],
1121 options = [_info objectForKey:CPOptionsKey],
1122 newValue = [destination valueForKeyPath:keyPath],
1123 isPlaceholder = CPIsControllerMarker(newValue);
1125 [_source removeAllItems];
1132 case CPMultipleValuesMarker:
1133 newValue = [options objectForKey:CPMultipleValuesPlaceholderBindingOption] || [];
1136 case CPNoSelectionMarker:
1137 newValue = [options objectForKey:CPNoSelectionPlaceholderBindingOption] || [];
1140 case CPNotApplicableMarker:
1141 if ([options objectForKey:CPRaisesForNotApplicableKeysBindingOption])
1143 reason:@"can't transform non applicable key on: " + _source + " value: " + newValue];
1145 newValue = [options objectForKey:CPNotApplicablePlaceholderBindingOption] || [];
1149 newValue = [options objectForKey:CPNullPlaceholderBindingOption] || [];
1153 if (![newValue isKindOfClass:[CPArray
class]])
1157 newValue = [
self transformValue:newValue withOptions:options];
1161 case CPContentBinding:
1162 [_source setContent:newValue];
1165 case CPContentValuesBinding:
1166 [_source setContentValues:newValue];
1173 @implementation _CPComboBoxPopUpButton :
CPView
1178 - (id)initWithFrame:(CGRect)aFrame comboBox:(
CPComboBox)aComboBox
1180 self = [
super initWithFrame:aFrame];
1183 _comboBox = aComboBox;
1188 - (void)mouseDown:(
CPEvent)theEvent
1190 [_comboBox _popUpButtonWasClicked];
1193 - (BOOL)acceptsFirstResponder