41 _CPPopUpList _listDelegate;
42 CPComboBoxDataSource _dataSource;
46 int _numberOfVisibleItems;
48 BOOL _hasVerticalScroller;
50 BOOL _popUpButtonCausedResign;
63 + (Class)_binderClassForBinding:(
CPString)theBinding
66 return [_CPComboBoxContentBinder class];
68 return [
super _binderClassForBinding:theBinding];
71 - (id)initWithFrame:(CGRect)aFrame
84 _listClass = [_CPPopUpList class];
90 _hasVerticalScroller = YES;
91 _selectedStringValue =
@"";
92 _popUpButtonCausedResign = NO;
95 [
self setBordered:YES];
96 [
self setBezeled:YES];
97 [
self setEditable:YES];
98 [
self setThemeState:CPComboBoxStateButtonBordered];
101 #pragma mark Setting Display Attributes
103 - (BOOL)hasVerticalScroller
105 return _hasVerticalScroller;
108 - (void)setHasVerticalScroller:(BOOL)flag
112 if (_hasVerticalScroller === flag)
115 _hasVerticalScroller = flag;
116 [[_listDelegate scrollView] setHasVerticalScroller:flag];
119 - (CGSize)intercellSpacing
121 return [[_listDelegate tableView] intercellSpacing];
124 - (void)setIntercellSpacing:(CGSize)aSize
126 [[_listDelegate tableView] setIntercellSpacing:aSize];
129 - (BOOL)isButtonBordered
134 - (void)setButtonBordered:(BOOL)flag
144 return [[_listDelegate tableView] rowHeight];
147 - (void)setItemHeight:(
float)itemHeight
149 [[_listDelegate tableView] setRowHeight:itemHeight];
152 [[_listDelegate tableView] reloadData];
155 - (int)numberOfVisibleItems
157 return _numberOfVisibleItems;
160 - (void)setNumberOfVisibleItems:(
int)visibleItems
163 _numberOfVisibleItems = MAX(visibleItems, 1);
166 #pragma mark Setting a Delegate
168 - (
id < CPComboBoxDelegate >)delegate
179 - (void)setDelegate:(
id < CPComboBoxDelegate >)aDelegate
183 if (aDelegate === delegate)
190 [defaultCenter removeObserver:delegate name:CPComboBoxSelectionIsChangingNotification object:self];
191 [defaultCenter removeObserver:delegate name:CPComboBoxSelectionDidChangeNotification object:self];
192 [defaultCenter removeObserver:delegate name:CPComboBoxWillDismissNotification object:self];
193 [defaultCenter removeObserver:delegate name:CPComboBoxWillPopUpNotification object:self];
198 if ([aDelegate respondsToSelector:
@selector(comboBoxSelectionIsChanging:)])
199 [defaultCenter addObserver:delegate
200 selector:@selector(comboBoxSelectionIsChanging:)
201 name:CPComboBoxSelectionIsChangingNotification
204 if ([aDelegate respondsToSelector:
@selector(comboBoxSelectionDidChange:)])
205 [defaultCenter addObserver:delegate
206 selector:@selector(comboBoxSelectionDidChange:)
207 name:CPComboBoxSelectionDidChangeNotification
210 if ([aDelegate respondsToSelector:
@selector(comboBoxWillPopUp:)])
211 [defaultCenter addObserver:delegate
212 selector:@selector(comboBoxWillPopUp:)
213 name:CPComboBoxWillPopUpNotification
216 if ([aDelegate respondsToSelector:
@selector(comboBoxWillDismiss:)])
217 [defaultCenter addObserver:delegate
218 selector:@selector(comboBoxWillDissmis:)
219 name:CPComboBoxWillDismissNotification
226 #pragma mark Setting a Data Source
228 - (
id < CPComboBoxDataSource >)dataSource
230 if (!_usesDataSource)
231 [
self _dataSourceWarningForMethod:_cmd condition:NO];
236 - (void)setDataSource:(
id < CPComboBoxDataSource >)aSource
238 if (!_usesDataSource)
239 [
self _dataSourceWarningForMethod:_cmd condition:NO];
240 else if (_dataSource !== aSource)
242 if (![aSource respondsToSelector:
@selector(numberOfItemsInComboBox:)] ||
243 ![aSource respondsToSelector:
@selector(comboBox:objectValueForItemAtIndex:)])
245 CPLog.warn(
"Illegal %s data source (%s). Must implement numberOfItemsInComboBox: and comboBox:objectValueForItemAtIndex:", [
self className], [aSource description]);
248 _dataSource = aSource;
252 - (BOOL)usesDataSource
254 return _usesDataSource;
257 - (void)setUsesDataSource:(BOOL)flag
261 if (_usesDataSource === flag)
264 _usesDataSource = flag;
268 [_items removeAllObjects];
273 #pragma mark Working with an Internal List
275 - (void)addItemsWithObjectValues:(
CPArray)objects
277 [_items addObjectsFromArray:objects];
282 - (void)addItemWithObjectValue:(
id)anObject
284 [_items addObject:anObject];
289 - (void)insertItemWithObjectValue:(
id)anObject atIndex:(
int)anIndex
293 [
self _dataSourceWarningForMethod:_cmd condition:YES];
295 [_items insertObject:anObject atIndex:anIndex];
309 [
self _dataSourceWarningForMethod:_cmd condition:YES];
314 - (void)removeAllItems
316 [_items removeAllObjects];
321 - (void)removeItemAtIndex:(
int)index
325 [
self _dataSourceWarningForMethod:_cmd condition:YES];
327 [_items removeObjectAtIndex:index];
331 - (void)removeItemWithObjectValue:(
id)anObject
333 [_items removeObject:anObject];
341 return [_dataSource numberOfItemsInComboBox:self];
343 return _items.length;
346 #pragma mark Manipulating the Displayed List
351 - (_CPPopUpList)listDelegate
353 return _listDelegate;
361 - (void)setListDelegate:(_CPPopUpList)aDelegate
363 if (_listDelegate === aDelegate)
370 [defaultCenter removeObserver:self name:_CPPopUpListWillPopUpNotification object:_listDelegate];
371 [defaultCenter removeObserver:self name:_CPPopUpListWillDismissNotification object:_listDelegate];
372 [defaultCenter removeObserver:self name:_CPPopUpListDidDismissNotification object:_listDelegate];
373 [defaultCenter removeObserver:self name:_CPPopUpListItemWasClickedNotification object:_listDelegate];
375 var oldTableView = [_listDelegate tableView];
379 [defaultCenter removeObserver:self name:CPTableViewSelectionIsChangingNotification object:oldTableView];
380 [defaultCenter removeObserver:self name:CPTableViewSelectionDidChangeNotification object:oldTableView];
384 _listDelegate = aDelegate;
386 [defaultCenter addObserver:self
387 selector:@selector(comboBoxWillPopUp:)
388 name:_CPPopUpListWillPopUpNotification
389 object:_listDelegate];
391 [defaultCenter addObserver:self
392 selector:@selector(comboBoxWillDismiss:)
393 name:_CPPopUpListWillDismissNotification
394 object:_listDelegate];
396 [defaultCenter addObserver:self
397 selector:@selector(listDidDismiss:)
398 name:_CPPopUpListDidDismissNotification
399 object:_listDelegate];
401 [defaultCenter addObserver:self
402 selector:@selector(itemWasClicked:)
403 name:_CPPopUpListItemWasClickedNotification
404 object:_listDelegate];
406 [[_listDelegate scrollView] setHasVerticalScroller:_hasVerticalScroller];
408 var
tableView = [_listDelegate tableView];
410 [defaultCenter addObserver:self
411 selector:@selector(comboBoxSelectionIsChanging:)
412 name:CPTableViewSelectionIsChangingNotification
415 [defaultCenter addObserver:self
416 selector:@selector(comboBoxSelectionDidChange:)
417 name:CPTableViewSelectionDidChangeNotification
421 [_listDelegate setFont:[
self font]];
422 [_listDelegate setAlignment:[
self alignment]];
425 - (int)indexOfItemWithObjectValue:(
id)anObject
428 [
self _dataSourceWarningForMethod:_cmd condition:YES];
430 return [_items indexOfObject:anObject];
433 - (id)itemObjectValueAtIndex:(
int)index
436 [
self _dataSourceWarningForMethod:_cmd condition:YES];
438 return [_items objectAtIndex:index];
441 - (void)noteNumberOfItemsChanged
443 [[_listDelegate tableView] noteNumberOfRowsChanged];
446 - (void)scrollItemAtIndexToTop:(
int)index
448 [_listDelegate scrollItemAtIndexToTop:index];
451 - (void)scrollItemAtIndexToVisible:(
int)index
453 [[_listDelegate tableView] scrollRowToVisible:index];
458 [[_listDelegate tableView] reloadData];
467 [
self _selectMatchingItem];
478 [_listDelegate popUpRelativeToRect:[
self _borderFrame] view:self offset:CPComboBoxFocusRingWidth - 1];
482 - (BOOL)listIsVisible
484 return _listDelegate ? [_listDelegate isVisible] : NO;
488 - (void)reloadDataSourceForSelector:(
SEL)cmd
491 [
self _dataSourceWarningForMethod:cmd condition:YES]
501 - (BOOL)takeStringValueFromList
503 if (_usesDataSource && _dataSource && [_dataSource numberOfItemsInComboBox:
self] === 0)
506 var selectedStringValue = [_listDelegate selectedStringValue];
508 if (selectedStringValue === nil)
511 _selectedStringValue = selectedStringValue;
514 [
self _reverseSetBinding];
538 #pragma mark Manipulating the Selection
540 - (void)deselectItemAtIndex:(
int)index
542 var table = [_listDelegate tableView],
543 row = [table selectedRow];
548 [table deselectRow:index];
551 - (int)indexOfSelectedItem
553 return [[_listDelegate tableView] selectedRow];
556 - (id)objectValueOfSelectedItem
558 var row = [[_listDelegate tableView] selectedRow];
563 [
self _dataSourceWarningForMethod:_cmd condition:YES];
571 - (void)selectItemAtIndex:(
int)index
573 var table = [_listDelegate tableView],
574 row = [table selectedRow];
582 - (void)selectItemWithObjectValue:(
id)anObject
590 #pragma mark Completing the Text Field
597 - (void)setCompletes:(BOOL)flag
608 var index = [_items indexOfObjectPassingTest:CPComboBoxCompletionTest context:substring];
610 return index !==
CPNotFound ? _items[index] : nil;
618 - (BOOL)forceSelection
620 return _forceSelection;
632 - (void)setForceSelection:(BOOL)flag
634 _forceSelection = !!flag;
637 #pragma mark CPTextField Delegate Methods and Overrides
640 - (BOOL)sendAction:(
SEL)anAction to:(
id)anObject
646 if ([
self listIsVisible])
649 [_listDelegate close];
656 - (void)setObjectValue:(
id)object
666 var theEvent = events[0];
673 if (![theEvent _couldBeKeyEquivalent] && [theEvent characters].charAt(0) !==
CPDeleteCharacter)
675 var value = [
self _inputElement].value,
678 _canComplete = CPMaxRange(selectedRange) === value.length;
686 - (void)paste:(
id)sender
691 var value = [
self _inputElement].value,
694 _canComplete = CPMaxRange(selectedRange) === value.length;
699 [
super paste:sender];
711 newString = uncompletedString;
713 if (_completes && _canComplete)
717 if (newString && newString.length > uncompletedString.length)
720 [
self setSelectedRange:CPMakeRange(uncompletedString.length, newString.length - uncompletedString.length)];
724 [
self _selectMatchingItem];
736 if ([[
self window] firstResponder] ===
self)
743 if (![
self listIsVisible])
751 if ([
self listIsVisible])
755 if (_forceSelection && ([
self _inputElement].value !== _selectedStringValue))
761 if ([_listDelegate performKeyEquivalent:anEvent])
769 - (BOOL)resignFirstResponder
771 var buttonCausedResign = _popUpButtonCausedResign;
773 _popUpButtonCausedResign = NO;
779 var shouldResign = !buttonCausedResign && (!_listDelegate || [_listDelegate controllingViewShouldResign]);
786 var element = [
self _inputElement];
787 window.setTimeout(
function() { element.focus(); }, 0);
794 [_listDelegate close];
802 if (_forceSelection && ![value
isEqual:_selectedStringValue])
806 _selectedStringValue =
@"";
814 [_listDelegate setFont:aFont];
817 - (void)setAlignment:(CPTextAlignment)alignment
820 [_listDelegate setAlignment:alignment];
823 #pragma mark Pop Up Button Layout
825 - (CGRect)popupButtonRectForBounds:(CGRect)bounds
830 bounds.origin.x = CGRectGetMaxX(bounds) - borderInset.right - buttonSize.width;
831 bounds.origin.y += borderInset.top;
833 bounds.
size.width = buttonSize.width;
834 bounds.size.height = buttonSize.height;
839 - (CGRect)rectForEphemeralSubviewNamed:(
CPString)aName
841 if (aName ===
"popup-button-view")
849 if (aName ===
"popup-button-view")
851 var view = [[_CPComboBoxPopUpButton alloc] initWithFrame:_CGRectMakeZero() comboBox:self];
859 - (void)layoutSubviews
868 #pragma mark Internal Helpers
871 - (void)_dataSourceWarningForMethod:(
SEL)cmd condition:(
CPString)flag
873 CPLog.warn(
"-[%s %s] should not be called when usesDataSource is set to %s", [
self className], cmd, flag ?
"YES" :
"NO");
880 - (void)_selectMatchingItem
883 stringValue = [
self stringValue];
887 if (_dataSource && [_dataSource respondsToSelector:
@selector(comboBox:indexOfItemWithStringValue:)])
888 index = [_dataSource comboBox:self indexOfItemWithStringValue:stringValue]
891 index = [
self indexOfItemWithObjectValue:stringValue];
893 [_listDelegate selectRow:index];
898 [_listDelegate scrollItemAtIndexToTop:index];
899 _selectedStringValue = stringValue;
907 - (CGRect)_borderFrame
909 var inset = [
self currentValueForThemeAttribute:@"border-inset"],
910 frame = [
self bounds];
912 frame.origin.x += inset.left;
913 frame.origin.y += inset.top;
914 frame.size.width -= inset.left + inset.right;
915 frame.size.height -= inset.top + inset.bottom;
921 - (void)_popUpButtonWasClicked
923 if (![
self isEnabled])
928 var firstResponder = [[
self window] firstResponder];
930 _popUpButtonCausedResign = firstResponder ===
self;
932 if ([
self listIsVisible])
933 [_listDelegate close];
936 if (firstResponder !==
self)
937 [[
self window] makeFirstResponder:self];
978 if ([_dataSource respondsToSelector:
@selector(comboBox:completedString:)])
979 return [_dataSource comboBox:self completedString:uncompletedString];
986 @implementation CPComboBox (_CPPopUpListDataSource)
988 - (int)numberOfItemsInList:(_CPPopUpList)aList
990 return [
self numberOfItems];
993 - (int)numberOfVisibleItemsInList:(_CPPopUpList)aList
995 return [
self numberOfVisibleItems];
998 - (id)list:(_CPPopUpList)aList objectValueForItemAtIndex:(
int)index
1000 if (_usesDataSource)
1001 return [_dataSource comboBox:self objectValueForItemAtIndex:index];
1003 return _items[index];
1006 - (id)list:(_CPPopUpList)aList displayValueForObjectValue:(
id)aValue
1008 return aValue ||
@"";
1011 - (
CPString)list:(_CPPopUpList)aList stringValueForObjectValue:(
id)aValue
1013 return String(aValue);
1040 values.push([object description]);
1066 [
self _initComboBox];
1068 _items = [aCoder decodeObjectForKey:CPComboBoxItemsKey];
1069 _listDelegate = [aCoder decodeObjectForKey:CPComboBoxListKey];
1070 _delegate = [aCoder decodeObjectForKey:CPComboBoxDelegateKey];
1071 _dataSource = [aCoder decodeObjectForKey:CPComboBoxDataSourceKey];
1072 _usesDataSource = [aCoder decodeBoolForKey:CPComboBoxUsesDataSourceKey];
1073 _completes = [aCoder decodeBoolForKey:CPComboBoxCompletesKey];
1074 _numberOfVisibleItems = [aCoder decodeIntForKey:CPComboBoxNumberOfVisibleItemsKey];
1075 _hasVerticalScroller = [aCoder decodeBoolForKey:CPComboBoxHasVerticalScrollerKey];
1086 [aCoder encodeObject:_items forKey:CPComboBoxItemsKey];
1087 [aCoder encodeObject:_listDelegate forKey:CPComboBoxListKey];
1088 [aCoder encodeObject:_delegate forKey:CPComboBoxDelegateKey];
1089 [aCoder encodeObject:_dataSource forKey:CPComboBoxDataSourceKey];
1090 [aCoder encodeBool:_usesDataSource forKey:CPComboBoxUsesDataSourceKey];
1091 [aCoder encodeBool:_completes forKey:CPComboBoxCompletesKey];
1092 [aCoder encodeInt:_numberOfVisibleItems forKey:CPComboBoxNumberOfVisibleItemsKey];
1093 [aCoder encodeBool:_hasVerticalScroller forKey:CPComboBoxHasVerticalScrollerKey];
1094 [aCoder encodeBool:[
self isButtonBordered] forKey:CPComboBoxButtonBorderedKey];
1102 return object.toString().indexOf(context) === 0;
1109 @implementation _CPComboBoxContentBinder :
CPBinder
1114 - (void)setValueFor:(
CPString)theBinding
1116 var destination = [_info objectForKey:CPObservedObjectKey],
1117 keyPath = [_info objectForKey:CPObservedKeyPathKey],
1118 options = [_info objectForKey:CPOptionsKey],
1119 newValue = [destination valueForKeyPath:keyPath],
1122 [_source removeAllItems];
1130 newValue = [options objectForKey:CPMultipleValuesPlaceholderBindingOption] || [];
1134 newValue = [options objectForKey:CPNoSelectionPlaceholderBindingOption] || [];
1140 reason:@"can't transform non applicable key on: " + _source + " value: " + newValue];
1142 newValue = [options objectForKey:CPNotApplicablePlaceholderBindingOption] || [];
1146 newValue = [options objectForKey:CPNullPlaceholderBindingOption] || [];
1150 if (![newValue isKindOfClass:[
CPArray class]])
1154 newValue = [
self transformValue:newValue withOptions:options];
1168 @implementation _CPComboBoxPopUpButton :
CPView
1173 - (id)initWithFrame:(CGRect)aFrame comboBox:(
CPComboBox)aComboBox
1175 self = [
super initWithFrame:aFrame];
1178 _comboBox = aComboBox;
1183 - (void)mouseDown:(
CPEvent)theEvent
1185 [_comboBox _popUpButtonWasClicked];
1188 - (BOOL)acceptsFirstResponder