40 - (id)comboBox:(
CPComboBox)aComboBox objectValueForItemAtIndex:(
int)index;
41 - (int)comboBox:(
CPComboBox)aComboBox indexOfItemWithStringValue:(
CPString)stringValue;
42 - (int)numberOfItemsInComboBox:(
CPComboBox)aComboBox;
69 BOOL _hasVerticalScroller;
70 BOOL _popUpButtonCausedResign;
72 CGSize _intercellSpacing;
74 id <CPComboBoxDataSource> _dataSource;
75 CPInteger _implementedDelegateComboBoxMethods;
78 int _numberOfVisibleItems;
79 _CPPopUpList _listDelegate;
90 @"popup-button-size": CGSizeMake(21.0, 29.0),
91 @"border-inset": CGInsetMake(3.0, 3.0, 3.0, 3.0),
95 + (Class)_binderClassForBinding:(
CPString)aBinding
97 if (aBinding === CPContentBinding || aBinding === CPContentValuesBinding)
98 return [_CPComboBoxContentBinder class];
100 return [
super _binderClassForBinding:aBinding];
103 - (id)initWithFrame:(CGRect)aFrame
108 [
self _initComboBox];
113 - (void)_initComboBox
115 _items = [CPArray array];
117 _usesDataSource = NO;
121 _forceSelection = NO;
122 _hasVerticalScroller = YES;
123 _selectedStringValue =
@"";
124 _popUpButtonCausedResign = NO;
127 [
self setBordered:YES];
128 [
self setBezeled:YES];
129 [
self setEditable:YES];
130 [
self setThemeState:CPComboBoxStateButtonBordered];
133 #pragma mark Setting Display Attributes 135 - (BOOL)hasVerticalScroller
137 return _hasVerticalScroller;
140 - (void)setHasVerticalScroller:(BOOL)flag
144 if (_hasVerticalScroller === flag)
147 _hasVerticalScroller = flag;
150 [[_listDelegate scrollView] setHasVerticalScroller:_hasVerticalScroller];
153 - (CGSize)intercellSpacing
155 return [[_listDelegate tableView] intercellSpacing];
158 - (void)setIntercellSpacing:(CGSize)aSize
160 if (_intercellSpacing && CGSizeEqualToSize(aSize, _intercellSpacing))
163 _intercellSpacing = aSize;
166 [[_listDelegate tableView] setIntercellSpacing:_intercellSpacing];
169 - (BOOL)isButtonBordered
171 return [
self hasThemeState:CPComboBoxStateButtonBordered];
174 - (void)setButtonBordered:(BOOL)flag
184 return [[_listDelegate tableView] rowHeight];
187 - (void)setItemHeight:(
float)itemHeight
189 if (itemHeight === _itemHeight)
192 _itemHeight = itemHeight;
196 [[_listDelegate tableView] setRowHeight:_itemHeight];
199 [[_listDelegate tableView] reloadData];
203 - (int)numberOfVisibleItems
205 return _numberOfVisibleItems;
208 - (void)setNumberOfVisibleItems:(
int)visibleItems
211 _numberOfVisibleItems = MAX(visibleItems, 1);
214 #pragma mark Setting a Delegate 227 - (void)setDelegate:(
id <CPComboBoxDelegate>)aDelegate
234 _implementedDelegateComboBoxMethods = 0;
238 if ([aDelegate respondsToSelector:
@selector(comboBoxSelectionIsChanging:)])
241 if ([aDelegate respondsToSelector:
@selector(comboBoxSelectionDidChange:)])
244 if ([aDelegate respondsToSelector:
@selector(comboBoxWillPopUp:)])
247 if ([aDelegate respondsToSelector:
@selector(comboBoxWillDismiss:)])
254 #pragma mark Setting a Data Source 256 - (
id <CPComboBoxDataSource>)dataSource
258 if (!_usesDataSource)
259 [
self _dataSourceWarningForMethod:_cmd condition:NO];
264 - (void)setDataSource:(
id <CPComboBoxDataSource>)aSource
266 if (!_usesDataSource)
268 [
self _dataSourceWarningForMethod:_cmd condition:NO];
270 else if (_dataSource !== aSource)
272 if (![aSource respondsToSelector:
@selector(numberOfItemsInComboBox:)] ||
273 ![aSource respondsToSelector:
@selector(comboBox:objectValueForItemAtIndex:)])
275 CPLog.warn(
"Illegal %s data source (%s). Must implement numberOfItemsInComboBox: and comboBox:objectValueForItemAtIndex:", [
self className], [aSource
description]);
279 _dataSource = aSource;
284 - (BOOL)usesDataSource
286 return _usesDataSource;
289 - (void)setUsesDataSource:(BOOL)flag
293 if (_usesDataSource === flag)
296 _usesDataSource = flag;
300 [_items removeAllObjects];
305 #pragma mark Working with an Internal List 307 - (void)addItemsWithObjectValues:(CPArray)objects
309 [_items addObjectsFromArray:objects];
314 - (void)addItemWithObjectValue:(
id)anObject
316 [_items addObject:anObject];
321 - (void)insertItemWithObjectValue:(
id)anObject atIndex:(
int)anIndex
325 [
self _dataSourceWarningForMethod:_cmd condition:YES];
327 [_items insertObject:anObject atIndex:anIndex];
338 - (CPArray)objectValues
341 [
self _dataSourceWarningForMethod:_cmd condition:YES];
346 - (void)removeAllItems
348 [_items removeAllObjects];
353 - (void)removeItemAtIndex:(
int)index
357 [
self _dataSourceWarningForMethod:_cmd condition:YES];
359 [_items removeObjectAtIndex:index];
363 - (void)removeItemWithObjectValue:(
id)anObject
365 [_items removeObject:anObject];
373 return [_dataSource numberOfItemsInComboBox:self];
375 return _items.length;
378 #pragma mark Manipulating the Displayed List 383 - (_CPPopUpList)listDelegate
385 return _listDelegate;
393 - (void)setListDelegate:(_CPPopUpList)aDelegate
395 if (_listDelegate === aDelegate)
398 [
self _removeObserversForListDelegate:_listDelegate];
400 _listDelegate = aDelegate;
404 [
self _addObserversForListDelegate:_listDelegate]
407 [_listDelegate setFont:[
self font]];
408 [_listDelegate setAlignment:[
self alignment]];
410 [[_listDelegate scrollView] setHasVerticalScroller:_hasVerticalScroller];
412 if (_intercellSpacing)
413 [[_listDelegate tableView] setIntercellSpacing:_intercellSpacing];
416 [[_listDelegate tableView] setRowHeight:_itemHeight];
419 - (void)_addObserversForListDelegate:(_CPPopUpList)aDelegate
426 [defaultCenter addObserver:self
427 selector:@selector(comboBoxWillPopUp:)
428 name:_CPPopUpListWillPopUpNotification
431 [defaultCenter addObserver:self
432 selector:@selector(comboBoxWillDismiss:)
433 name:_CPPopUpListWillDismissNotification
436 [defaultCenter addObserver:self
437 selector:@selector(listDidDismiss:)
438 name:_CPPopUpListDidDismissNotification
441 [defaultCenter addObserver:self
442 selector:@selector(itemWasClicked:)
443 name:_CPPopUpListItemWasClickedNotification
446 [[aDelegate scrollView] setHasVerticalScroller:_hasVerticalScroller];
450 [defaultCenter addObserver:self
451 selector:@selector(comboBoxSelectionIsChanging:)
452 name:CPTableViewSelectionIsChangingNotification
455 [defaultCenter addObserver:self
456 selector:@selector(comboBoxSelectionDidChange:)
457 name:CPTableViewSelectionDidChangeNotification
461 - (void)_removeObserversForListDelegate:(_CPPopUpList)aDelegate
468 [defaultCenter removeObserver:self name:_CPPopUpListWillPopUpNotification object:aDelegate];
469 [defaultCenter removeObserver:self name:_CPPopUpListWillDismissNotification object:aDelegate];
470 [defaultCenter removeObserver:self name:_CPPopUpListDidDismissNotification object:aDelegate];
471 [defaultCenter removeObserver:self name:_CPPopUpListItemWasClickedNotification object:aDelegate];
473 var oldTableView = [aDelegate tableView];
477 [defaultCenter removeObserver:self name:CPTableViewSelectionIsChangingNotification object:oldTableView];
478 [defaultCenter removeObserver:self name:CPTableViewSelectionDidChangeNotification object:oldTableView];
482 - (int)indexOfItemWithObjectValue:(
id)anObject
485 [
self _dataSourceWarningForMethod:_cmd condition:YES];
487 return [_items indexOfObject:anObject];
490 - (id)itemObjectValueAtIndex:(
int)index
493 [
self _dataSourceWarningForMethod:_cmd condition:YES];
495 return [_items objectAtIndex:index];
498 - (void)noteNumberOfItemsChanged
500 [[_listDelegate tableView] noteNumberOfRowsChanged];
503 - (void)scrollItemAtIndexToTop:(
int)index
505 [_listDelegate scrollItemAtIndexToTop:index];
508 - (void)scrollItemAtIndexToVisible:(
int)index
510 [[_listDelegate tableView] scrollRowToVisible:index];
515 [[_listDelegate tableView] reloadData];
528 var inset = [
self currentValueForThemeAttribute:@"border-inset"];
533 [_listDelegate popUpRelativeToRect:[
self _borderFrame] view:self offset:CPComboBoxFocusRingWidth - 1];
534 [
self _selectMatchingItem];
538 - (BOOL)listIsVisible
540 return _listDelegate ? [_listDelegate isVisible] : NO;
544 - (void)reloadDataSourceForSelector:(
SEL)cmd
547 [
self _dataSourceWarningForMethod:cmd condition:YES]
557 - (BOOL)takeStringValueFromList
559 if (_usesDataSource && _dataSource && [_dataSource numberOfItemsInComboBox:
self] === 0)
562 var selectedStringValue = [_listDelegate selectedStringValue];
564 if (selectedStringValue === nil)
567 _selectedStringValue = selectedStringValue;
570 [
self _reverseSetBinding];
594 #pragma mark Manipulating the Selection 596 - (void)deselectItemAtIndex:(
int)index
598 var table = [_listDelegate tableView],
599 row = [table selectedRow];
604 [table deselectRow:index];
607 - (int)indexOfSelectedItem
609 return [[_listDelegate tableView] selectedRow];
612 - (id)objectValueOfSelectedItem
614 var row = [[_listDelegate tableView] selectedRow];
619 [
self _dataSourceWarningForMethod:_cmd condition:YES];
627 - (void)selectItemAtIndex:(
int)index
629 var table = [_listDelegate tableView],
630 row = [table selectedRow];
638 - (void)selectItemWithObjectValue:(
id)anObject
646 #pragma mark Completing the Text Field 653 - (void)setCompletes:(BOOL)flag
664 var index = [_items indexOfObjectPassingTest:CPComboBoxCompletionTest context:substring];
666 return index !==
CPNotFound ? _items[index] : nil;
674 - (BOOL)forceSelection
676 return _forceSelection;
688 - (void)setForceSelection:(BOOL)flag
690 _forceSelection = !!flag;
693 #pragma mark CPTextField Delegate Methods and Overrides 696 - (BOOL)sendAction:(
SEL)anAction to:(
id)anObject
702 if ([
self listIsVisible])
705 [_listDelegate close];
712 - (void)setObjectValue:(
id)object
720 - (void)interpretKeyEvents:(CPArray)events
722 var theEvent = events[0];
729 if (![theEvent _couldBeKeyEquivalent] && [theEvent characters].charAt(0) !==
CPDeleteCharacter)
731 var value = [
self _inputElement].value,
734 _canComplete =
CPMaxRange(selectedRange) === value.length;
738 [
super interpretKeyEvents:events];
742 - (void)paste:(
id)sender
747 var value = [
self _inputElement].value,
750 _canComplete =
CPMaxRange(selectedRange) === value.length;
755 [
super paste:sender];
767 newString = uncompletedString;
769 if (_completes && _canComplete)
773 if (newString && newString.length > uncompletedString.
length)
776 [
self setSelectedRange:CPMakeRange(uncompletedString.length, newString.length - uncompletedString.length)];
780 [
self _selectMatchingItem];
792 if ([[
self window] firstResponder] ===
self)
799 if (![
self listIsVisible])
807 if ([
self listIsVisible])
811 if (_forceSelection && ([
self _inputElement].value !== _selectedStringValue))
817 if ([_listDelegate performKeyEquivalent:anEvent])
825 - (BOOL)resignFirstResponder
827 var buttonCausedResign = _popUpButtonCausedResign;
829 _popUpButtonCausedResign = NO;
835 var shouldResign = !buttonCausedResign && (!_listDelegate || [_listDelegate controllingViewShouldResign]);
842 var element = [
self _inputElement];
847 var previousScrollingOrigin = [
self _scrollToVisibleRectAndReturnPreviousOrigin];
851 [
self _restorePreviousScrollingOrigin:previousScrollingOrigin];
860 [_listDelegate close];
868 if (_forceSelection && ![value
isEqual:_selectedStringValue])
872 _selectedStringValue =
@"";
882 [_listDelegate setFont:aFont];
885 - (void)setAlignment:(CPTextAlignment)alignment
890 [_listDelegate setAlignment:alignment];
893 #pragma mark Pop Up Button Layout 895 - (CGRect)popupButtonRectForBounds:(CGRect)bounds
897 var borderInset = [
self currentValueForThemeAttribute:@"border-inset"],
898 buttonSize = [
self currentValueForThemeAttribute:@"popup-button-size"];
900 bounds.origin.x = CGRectGetMaxX(
bounds) - borderInset.right - buttonSize.width;
901 bounds.origin.y += borderInset.top;
903 bounds.size.width = buttonSize.width;
904 bounds.size.height = buttonSize.height;
909 - (CGRect)rectForEphemeralSubviewNamed:(
CPString)aName
911 if (aName ===
"popup-button-view")
919 if (aName ===
"popup-button-view")
921 var view = [[_CPComboBoxPopUpButton alloc] initWithFrame:CGRectMakeZero() comboBox:self];
929 - (void)layoutSubviews
938 #pragma mark Internal Helpers 941 - (void)_dataSourceWarningForMethod:(
SEL)cmd condition:(
CPString)flag
943 CPLog.warn(
"-[%s %s] should not be called when usesDataSource is set to %s", [
self className], cmd, flag ?
"YES" :
"NO");
950 - (void)_selectMatchingItem
953 stringValue = [
self stringValue];
957 if (_dataSource && [_dataSource respondsToSelector:
@selector(comboBox:indexOfItemWithStringValue:)])
958 index = [_dataSource comboBox:self indexOfItemWithStringValue:stringValue]
962 index = [
self indexOfItemWithObjectValue:stringValue];
965 [_listDelegate selectRow:index];
970 [_listDelegate scrollItemAtIndexToTop:index];
971 _selectedStringValue = stringValue;
979 - (CGRect)_borderFrame
981 var inset = [
self currentValueForThemeAttribute:@"border-inset"],
982 frame = [
self bounds];
984 frame.origin.x += inset.left;
985 frame.origin.y += inset.top;
986 frame.size.width -= inset.left + inset.right;
987 frame.size.height -= inset.top + inset.bottom;
993 - (void)_popUpButtonWasClicked
995 if (![
self isEnabled])
1000 var firstResponder = [[
self window] firstResponder];
1002 _popUpButtonCausedResign = firstResponder ===
self;
1004 if ([
self listIsVisible])
1005 [_listDelegate close];
1008 if (firstResponder !==
self)
1009 [[
self window] makeFirstResponder:self];
1017 #pragma mark Observers method 1019 - (void)_addObservers
1024 [
super _addObservers];
1025 [
self _addObserversForListDelegate:_listDelegate];
1028 - (void)_removeObservers
1033 [
super _removeObservers];
1034 [
self _removeObserversForListDelegate:_listDelegate];
1084 if ([_dataSource respondsToSelector:
@selector(comboBox:completedString:)])
1085 return [_dataSource comboBox:self completedString:uncompletedString];
1092 @implementation CPComboBox (_CPPopUpListDataSource)
1094 - (int)numberOfItemsInList:(_CPPopUpList)aList
1096 return [
self numberOfItems];
1099 - (int)numberOfVisibleItemsInList:(_CPPopUpList)aList
1101 return [
self numberOfVisibleItems];
1104 - (id)list:(_CPPopUpList)aList objectValueForItemAtIndex:(
int)index
1106 if (_usesDataSource)
1107 return [_dataSource comboBox:self objectValueForItemAtIndex:index];
1109 return _items[index];
1112 - (id)list:(_CPPopUpList)aList displayValueForObjectValue:(
id)aValue
1114 return aValue ||
@"";
1117 - (
CPString)list:(_CPPopUpList)aList stringValueForObjectValue:(
id)aValue
1119 return String(aValue);
1127 - (void)setContentValues:(CPArray)anArray
1135 - (void)setContent:(CPArray)anArray
1142 var values = [anArray arrayByApplyingBlock:function(object)
1144 return [object description];
1170 [
self _initComboBox];
1172 _items = [aCoder decodeObjectForKey:CPComboBoxItemsKey];
1173 _listDelegate = [aCoder decodeObjectForKey:CPComboBoxListKey];
1174 _delegate = [aCoder decodeObjectForKey:CPComboBoxDelegateKey];
1175 _dataSource = [aCoder decodeObjectForKey:CPComboBoxDataSourceKey];
1176 _usesDataSource = [aCoder decodeBoolForKey:CPComboBoxUsesDataSourceKey];
1177 _completes = [aCoder decodeBoolForKey:CPComboBoxCompletesKey];
1178 _numberOfVisibleItems = [aCoder decodeIntForKey:CPComboBoxNumberOfVisibleItemsKey];
1179 _hasVerticalScroller = [aCoder decodeBoolForKey:CPComboBoxHasVerticalScrollerKey];
1190 [aCoder encodeObject:_items forKey:CPComboBoxItemsKey];
1191 [aCoder encodeObject:_listDelegate forKey:CPComboBoxListKey];
1192 [aCoder encodeObject:_delegate forKey:CPComboBoxDelegateKey];
1193 [aCoder encodeObject:_dataSource forKey:CPComboBoxDataSourceKey];
1194 [aCoder encodeBool:_usesDataSource forKey:CPComboBoxUsesDataSourceKey];
1195 [aCoder encodeBool:_completes forKey:CPComboBoxCompletesKey];
1196 [aCoder encodeInt:_numberOfVisibleItems forKey:CPComboBoxNumberOfVisibleItemsKey];
1197 [aCoder encodeBool:_hasVerticalScroller forKey:CPComboBoxHasVerticalScrollerKey];
1198 [aCoder encodeBool:[
self isButtonBordered] forKey:CPComboBoxButtonBorderedKey];
1206 return object.toString().indexOf(context) === 0;
1213 @implementation _CPComboBoxContentBinder :
CPBinder 1218 - (void)setValueFor:(
CPString)aBinding
1220 var destination = [_info objectForKey:CPObservedObjectKey],
1221 keyPath = [_info objectForKey:CPObservedKeyPathKey],
1222 options = [_info objectForKey:CPOptionsKey],
1223 newValue = [destination valueForKeyPath:keyPath],
1224 isPlaceholder = CPIsControllerMarker(newValue);
1226 [_source removeAllItems];
1233 case CPMultipleValuesMarker:
1234 newValue = [options objectForKey:CPMultipleValuesPlaceholderBindingOption] || [];
1237 case CPNoSelectionMarker:
1238 newValue = [options objectForKey:CPNoSelectionPlaceholderBindingOption] || [];
1241 case CPNotApplicableMarker:
1242 if ([options objectForKey:CPRaisesForNotApplicableKeysBindingOption])
1244 reason:@"can't transform non applicable key on: " + _source + " value: " + newValue];
1246 newValue = [options objectForKey:CPNotApplicablePlaceholderBindingOption] || [];
1250 newValue = [options objectForKey:CPNullPlaceholderBindingOption] || [];
1254 if (![newValue isKindOfClass:[CPArray
class]])
1258 newValue = [
self transformValue:newValue withOptions:options];
1262 case CPContentBinding:
1263 [_source setContent:newValue];
1266 case CPContentValuesBinding:
1267 [_source setContentValues:newValue];
1274 @implementation _CPComboBoxPopUpButton :
CPView 1279 - (id)initWithFrame:(CGRect)aFrame comboBox:(
CPComboBox)aComboBox
1281 self = [
super initWithFrame:aFrame];
1284 _comboBox = aComboBox;
1289 - (void)mouseDown:(
CPEvent)theEvent
1291 [_comboBox _popUpButtonWasClicked];
1294 - (BOOL)acceptsFirstResponder
Used to implement exception handling (creating & raising).
BOOL makeFirstResponder:(CPResponder aResponder)
void setFont:(CPFont aFont)
var CPComboBoxButtonSubview
CPString completedString:(CPString substring)
CPView createEphemeralSubviewNamed:(CPString aName)
var CPComboBoxDataSourceKey
void encodeWithCoder:(CPCoder aCoder)
void textDidChange:(CPNotification note)
BOOL setThemeState:(ThemeState aState)
id< CPComboBoxDelegate > delegate()
void addItemsWithObjectValues:(CPArray objects)
CPComboBoxStateButtonBordered
void setSelectedRange:(CPRange aRange)
void setButtonBordered:(BOOL flag)
var CPComboBoxHasVerticalScrollerKey
The main run loop for the application.
var CPComboBoxCompletionTest
var CPComboBoxDelegate_comboBoxWillPopUp_
CGRect popupButtonRectForBounds:(CGRect bounds)
void postNotificationName:object:(CPString aNotificationName, [object] id anObject)
void raise:reason:(CPString aName, [reason] CPString aReason)
CPComboBoxSelectionDidChangeNotification
A collection of unique integers.
CPString charactersIgnoringModifiers()
var CPComboBoxButtonBorderedKey
void selectItemAtIndex:(int index)
CPNotificationCenter defaultCenter()
A mutable key-value pair collection.
var CPComboBoxNumberOfVisibleItemsKey
CPComboBoxWillPopUpNotification
var CPComboBoxUsesDataSourceKey
id initWithName:object:userInfo:(CPString aNotificationName, [object] id anObject, [userInfo] CPDictionary aUserInfo)
function CPMaxRange(aRange)
An immutable string (collection of characters).
BOOL sendAction:to:(SEL anAction, [to] id anObject)
id initWithFrame:(CGRect aFrame)
void performBlock:argument:order:modes:(Function aBlock, [argument] id anArgument, [order] int anOrder, [modes] CPArray modes)
BOOL performKeyEquivalent:(CPEvent anEvent)
CGRect rectForEphemeralSubviewNamed:(CPString aName)
CPTextAlignment alignment()
void setStringValue:(CPString aString)
A notification that can be posted to a CPNotificationCenter.
var CPComboBoxDelegate_comboBoxSelectionDidChange_
var CPComboBoxCompletesKey
var CPComboBoxDelegate_comboBoxSelectionIsChanging_
void setUsesDataSource:(BOOL flag)
BOOL takeStringValueFromList()
void setObjectValue:(id aValue)
var CPComboBoxTextSubview
Defines methods for use when archiving & restoring (enc/decoding).
void setAlignment:(CPTextAlignment alignment)
BOOL unsetThemeState:(ThemeState aState)
CPString comboBoxCompletedString:(CPString uncompletedString)
void reloadDataSourceForSelector:(SEL cmd)
Sends messages (CPNotification) between objects.
CPComboBoxSelectionIsChangingNotification
BOOL resignFirstResponder()
BOOL sendAction:to:(SEL anAction, [to] id anObject)
var CPComboBoxDelegate_comboBoxWillDismiss_
var CPComboBoxDefaultNumberOfVisibleItems
var CPComboBoxFocusRingWidth
CPComboBoxWillDismissNotification
id initWithCoder:(CPCoder aCoder)
id indexSetWithIndex:(int anIndex)
int indexOfItemWithObjectValue:(id anObject)
void setDelegate:(id< CPTextFieldDelegate > aDelegate)
void setListDelegate:(_CPPopUpList aDelegate)
var CPComboBoxDelegateKey
CPView layoutEphemeralSubviewNamed:positioned:relativeToEphemeralSubviewNamed:(CPString aViewName, [positioned] CPWindowOrderingMode anOrderingMode, [relativeToEphemeralSubviewNamed] CPString relativeToViewName)
FrameUpdater prototype description