36 CPUInteger _selectedIndex;
37 CPRectEdge _preferredEdge;
42 return "popup-button";
45 + (CPSet)keyPathsForValuesAffectingSelectedIndex
47 return [CPSet setWithObject:@"objectValue"];
50 + (CPSet)keyPathsForValuesAffectingSelectedTag
52 return [CPSet setWithObject:@"objectValue"];
55 + (CPSet)keyPathsForValuesAffectingSelectedItem
57 return [CPSet setWithObject:@"objectValue"];
66 - (id)initWithFrame:(CGRect)aFrame pullsDown:(BOOL)shouldPullDown
76 [
self setValue:CPImageLeft forThemeAttribute:@"image-position"];
77 [
self setValue:CPLeftTextAlignment forThemeAttribute:@"alignment"];
78 [
self setValue:CPLineBreakByTruncatingTail forThemeAttribute:@"line-break-mode"];
80 [
self setMenu:[[
CPMenu alloc] initWithTitle:@""]];
85 [
self addObserver:self forKeyPath:@"menu.items" options:options context:nil];
86 [
self addObserver:self forKeyPath:@"_firstItem.changeCount" options:options context:nil];
87 [
self addObserver:self forKeyPath:@"selectedItem.changeCount" options:options context:nil];
93 - (id)initWithFrame:(CGRect)aFrame
108 - (void)setPullsDown:(BOOL)shouldPullDown
111 var changed = [
self setThemeState:CPPopUpButtonStatePullsDown];
113 var changed = [
self unsetThemeState:CPPopUpButtonStatePullsDown];
118 var items = [[
self menu] itemArray];
120 if ([items count] <= 0)
133 return [
self hasThemeState:CPPopUpButtonStatePullsDown];
143 [[
self menu] addItem:anItem];
152 [[
self menu] addItemWithTitle:aTitle action:NULL keyEquivalent:nil];
159 - (void)addItemsWithTitles:(CPArray)titles
162 count = [titles count];
164 for (; index < count; ++index)
165 [
self addItemWithTitle:titles[index]];
173 - (void)insertItemWithTitle:(
CPString)aTitle atIndex:(
int)anIndex
176 count = [items count];
179 if ([items[count] title] == aTitle)
182 [[
self menu] insertItemWithTitle:aTitle action:NULL keyEquivalent:nil atIndex:anIndex];
188 - (void)removeAllItems
190 [[
self menu] removeAllItems];
208 - (void)removeItemAtIndex:(
int)anIndex
210 [[
self menu] removeItemAtIndex:anIndex];
222 if (indexOfSelectedItem < 0 || indexOfSelectedItem > [
self numberOfItems] - 1)
225 return [[
self menu] itemAtIndex:indexOfSelectedItem];
239 - (int)indexOfSelectedItem
241 return _selectedIndex;
258 - (void)selectItemAtIndex:(CPUInteger)anIndex
263 - (void)setSelectedIndex:(CPUInteger)anIndex
268 - (CPUInteger)selectedIndex
277 - (void)setObjectValue:(
id)anIndex
281 anIndex = parseInt(+anIndex, 10);
283 if (indexOfSelectedItem === anIndex)
286 if (indexOfSelectedItem >= 0 && ![
self pullsDown])
289 _selectedIndex = anIndex;
291 if (indexOfSelectedItem >= 0 && ![
self pullsDown])
299 return _selectedIndex;
306 - (void)selectItemWithTag:(
int)aTag
327 return [[
self menu] numberOfItems];
335 return [[
self menu] itemArray];
344 return [[
self menu] itemAtIndex:anIndex];
353 return [[[
self menu] itemAtIndex:anIndex] title];
359 - (CPArray)itemTitles
364 count = [items count];
366 for (; index < count; ++index)
367 titles.push([items[index] title]);
378 var
menu = [
self menu],
379 itemIndex = [menu indexOfItemWithTitle:aTitle];
384 return [menu itemAtIndex:itemIndex];
392 return [[[
self menu] itemArray] lastObject];
402 return [[
self menu] indexOfItem:aMenuItem];
409 - (int)indexOfItemWithTag:(
int)aTag
411 return [[
self menu] indexOfItemWithTag:aTag];
420 return [[
self menu] indexOfItemWithTitle:aTitle];
429 - (int)indexOfItemWithRepresentedObject:(
id)anObject
431 return [[
self menu] indexOfItemWithRepresentedObject:anObject];
441 - (int)indexOfItemWithTarget:(
id)aTarget action:(
SEL)anAction
443 return [[
self menu] indexOfItemWithTarget:aTarget action:anAction];
452 - (CPRectEdge)preferredEdge
454 return _preferredEdge;
462 - (void)setPreferredEdge:(CPRectEdge)aRectEdge
464 _preferredEdge = aRectEdge;
474 if ([
self title] === aTitle)
477 if ([
self pullsDown])
479 var items = [[
self menu] itemArray];
481 if ([items count] <= 0)
486 [items[0] setTitle:aTitle];
521 - (void)synchronizeTitleAndSelectedItem
525 if ([
self pullsDown])
527 var items = [[
self menu] itemArray];
529 if ([items count] > 0)
539 - (void)observeValueForKeyPath:(
CPString)aKeyPath ofObject:(
id)anObject change:(
CPDictionary)changes context:(
id)aContext
543 if (!pullsDown && aKeyPath ===
@"selectedItem.changeCount" ||
544 pullsDown && (aKeyPath ===
@"_firstItem" || aKeyPath ===
@"_firstItem.changeCount"))
548 if (aKeyPath ===
@"menu")
550 aKeyPath =
@"menu.items";
552 [changes
setObject:CPKeyValueChangeSetting
forKey:CPKeyValueChangeKindKey];
553 [changes
setObject:[[
self menu] itemArray]
forKey:CPKeyValueChangeNewKey];
556 if (aKeyPath ===
@"menu.items")
558 var changeKind = [changes
objectForKey:CPKeyValueChangeKindKey],
564 indexes = [changes
objectForKey:CPKeyValueChangeIndexesKey];
566 if ([indexes containsIndex:0] && [
self pullsDown])
567 [
self _firstItemDidChange];
569 if (![
self pullsDown] && [indexes containsIndex:indexOfSelectedItem])
572 indexOfSelectedItem = 0;
577 while ((index = [indexes indexGreaterThanIndex:index]) !==
CPNotFound &&
578 index <= indexOfSelectedItem)
579 --indexOfSelectedItem;
587 var indexes = [changes
objectForKey:CPKeyValueChangeIndexesKey];
589 if (pullsDown && [indexes containsIndex:0] ||
590 !pullsDown && [indexes containsIndex:indexOfSelectedItem])
597 var newItems = [changes
objectForKey:CPKeyValueChangeNewKey];
599 [newItems enumerateObjectsUsingBlock:function(aMenuItem)
601 var action = [aMenuItem action];
604 [aMenuItem setAction:action = @selector(_popUpItemAction:)];
606 if (action === @selector(_popUpItemAction:))
607 [aMenuItem setTarget:self];
612 [
self _firstItemDidChange];
620 var indexes = [changes
objectForKey:CPKeyValueChangeIndexesKey];
622 if ([
self pullsDown] && [indexes containsIndex:0])
624 [
self _firstItemDidChange];
626 if ([
self numberOfItems] > 1)
631 while ((index = [indexes indexGreaterThanIndex:index]) !==
CPNotFound &&
632 index <= originalIndex)
639 if (indexOfSelectedItem < 0)
647 while ((index = [indexes indexGreaterThanIndex:index]) !==
CPNotFound &&
648 index <= indexOfSelectedItem)
649 ++indexOfSelectedItem;
662 if (![
self isEnabled] || ![
self numberOfItems])
665 var
menu = [
self menu];
668 if (
menu._lastCloseEvent === anEvent)
673 var bounds = [
self bounds],
674 minimumWidth = CGRectGetWidth(bounds);
677 [menu setFont:[
self font]];
679 if ([
self pullsDown])
681 var positionedItem = nil,
682 location = CGPointMake(0.0, CGRectGetMaxY(bounds) - 1);
688 standardLeftMargin = [_CPMenuWindow _standardLeftMargin] + [_CPMenuItemStandardView _standardLeftMargin],
689 location = CGPointMake(CGRectGetMinX(contentRect) - standardLeftMargin, 0.0);
691 minimumWidth += standardLeftMargin;
694 [menu _highlightItemAtIndex:CPNotFound];
697 [menu setMinimumWidth:minimumWidth];
700 _popUpMenuPositioningItem:positionedItem
702 topY:CGRectGetMinY(bounds)
703 bottomY:CGRectGetMaxY(bounds)
705 callback:function(aMenu)
709 var highlightedItem = [aMenu highlightedItem];
711 if ([highlightedItem _isSelectable])
737 - (void)_popUpItemAction:(
id)aSender
739 [
self sendAction:[
self action] to:[
self target]];
742 - (void)_firstItemDidChange
744 [
self willChangeValueForKey:@"_firstItem"];
745 [
self didChangeValueForKey:@"_firstItem"];
747 [[
self _firstItem] setHidden:YES];
752 if ([
self numberOfItems] <= 0)
755 return [[
self menu] itemAtIndex:0];
758 - (void)takeValueFromKeyPath:(
CPString)aKeyPath ofObjects:(CPArray)objects
760 var count = objects.length,
761 value = [objects[0] valueForKeyPath:aKeyPath];
767 if (value !== [objects[count] valueForKeyPath:aKeyPath])
771 - (void)_reverseSetBinding
773 [_CPPopUpButtonSelectionBinder reverseSetValueForObject:self];
775 [
super _reverseSetBinding];
782 + (Class)_binderClassForBinding:(
CPString)aBinding
784 if (aBinding == CPSelectedIndexBinding ||
785 aBinding == CPSelectedObjectBinding ||
786 aBinding == CPSelectedTagBinding ||
787 aBinding == CPSelectedValueBinding ||
788 aBinding == CPContentBinding ||
789 aBinding == CPContentObjectsBinding ||
790 aBinding == CPContentValuesBinding)
792 var capitalizedBinding = aBinding.charAt(0).toUpperCase() + aBinding.substr(1);
794 return [CPClassFromString(@"_CPPopUpButton" + capitalizedBinding + "Binder") class];
797 return [
super _binderClassForBinding:aBinding];
801 @implementation _CPPopUpButtonContentBinder :
CPBinder
806 - (CPInteger)_getInsertNullOffset
808 var options = [_info objectForKey:CPOptionsKey];
810 return [options objectForKey:CPInsertsNullPlaceholderBindingOption] ? 1 : 0;
815 var options = [_info objectForKey:CPOptionsKey],
816 placeholder = [options objectForKey:CPNullPlaceholderBindingOption] ||
@"";
818 if (placeholder === [
CPNull null])
824 - (id)transformValue:(CPArray)contentArray withOptions:(
CPDictionary)options
830 - (void)setValue:(CPArray)contentArray forBinding:(
CPString)aBinding
832 [
self _setContent:contentArray];
833 [
self _setContentValuesIfNeeded:contentArray];
836 - (id)valueForBinding:(
CPString)aBinding
838 return [
self _content];
841 - (void)_setContent:(CPArray)aValue
843 var count = [aValue count],
844 options = [_info objectForKey:CPOptionsKey],
845 offset = [
self _getInsertNullOffset];
847 if (count + offset != [_source numberOfItems])
849 [_source removeAllItems];
852 [_source addItemWithTitle:[
self _getNullPlaceholder]];
854 for (var i = 0; i < count; i++)
856 var item = [[
CPMenuItem alloc] initWithTitle:@"" action:NULL keyEquivalent:nil];
857 [
self _setValue:[aValue objectAtIndex:i] forItem:item withOptions:options];
858 [_source addItem:item];
863 for (var i = 0; i < count; i++)
865 [
self _setValue:[aValue objectAtIndex:i] forItem:[_source itemAtIndex:i + offset] withOptions:options];
870 - (void)_setContentValuesIfNeeded:(CPArray)values
872 var offset = [
self _getInsertNullOffset];
874 if (![_source infoForBinding:CPContentValuesBinding])
877 [[_source itemAtIndex:0] setTitle:[
self _getNullPlaceholder]];
879 var count = [values count];
881 for (var i = 0; i < count; i++)
882 [[_source itemAtIndex:i + offset] setTitle:[[values objectAtIndex:i] description]];
888 var value = [
self _transformValue:aValue withOptions:options];
889 [aMenuItem setRepresentedObject:value];
892 - (id)_transformValue:(
id)aValue withOptions:(
CPDictionary)options
894 return [
super transformValue:aValue withOptions:options];
899 return [_source valueForKeyPath:@"itemArray.representedObject"];
903 @implementation _CPPopUpButtonContentValuesBinder : _CPPopUpButtonContentBinder
908 - (void)setValue:(CPArray)aValue forBinding:(
CPString)aBinding
910 [
super _setContent:aValue];
915 if (aValue === [
CPNull null])
918 var value = [
self _transformValue:aValue withOptions:options];
919 [aMenuItem setTitle:value];
924 return [_source valueForKeyPath:@"itemArray.title"];
931 @implementation _CPPopUpButtonSelectionBinder :
CPBinder
938 self = [
super initWithBinding:aBinding name:aName to:aDestination keyPath:aKeyPath options:options from:aSource];
943 _selectionBinding = aName;
949 + (void)reverseSetValueForObject:(
id)aSource
952 [binder reverseSetValueFor:[binder _selectionBinding]];
955 - (void)setPlaceholderValue:(
id)aValue withMarker:(
CPString)aMarker forBinding:(
CPString)aBinding
957 [
self setValue:aValue forBinding:aBinding];
960 - (CPInteger)_getInsertNullOffset
962 var options = [[
CPBinder infoForBinding:CPContentBinding forObject:_source] objectForKey:CPOptionsKey];
964 return [options
objectForKey:CPInsertsNullPlaceholderBindingOption] ? 1 : 0;
968 @implementation _CPPopUpButtonSelectedIndexBinder : _CPPopUpButtonSelectionBinder
973 - (void)setValue:(
id)aValue forBinding:(
CPString)aBinding
975 [_source selectItemAtIndex:aValue + [
self _getInsertNullOffset]];
978 - (id)valueForBinding:(
CPString)aBinding
980 return [_source indexOfSelectedItem] - [self _getInsertNullOffset];
984 @implementation _CPPopUpButtonSelectedObjectBinder : _CPPopUpButtonSelectionBinder
989 - (void)setValue:(
id)aValue forBinding:(
CPString)aBinding
991 var index = [_source indexOfItemWithRepresentedObject:aValue],
992 offset = [
self _getInsertNullOffset];
1011 [_source selectItemAtIndex:index];
1014 - (id)valueForBinding:(
CPString)aBinding
1016 return [[_source selectedItem] representedObject];
1020 @implementation _CPPopUpButtonSelectedTagBinder : _CPPopUpButtonSelectionBinder
1025 - (void)setValue:(
id)aValue forBinding:(
CPString)aBinding
1027 [_source selectItemWithTag:aValue];
1030 - (id)valueForBinding:(
CPString)aBinding
1032 return [[_source selectedItem] tag];
1036 @implementation _CPPopUpButtonSelectedValueBinder : _CPPopUpButtonSelectionBinder
1041 - (void)setValue:(
id)aValue forBinding:(
CPString)aBinding
1043 [_source selectItemWithTitle:aValue];
1046 - (id)valueForBinding:(
CPString)aBinding
1048 return [_source titleOfSelectedItem];
1077 CPLog.warn(
self +
" was encoded with an older version of Cappuccino. Please nib2cib the original nib again or open and re-save in Atlas.");
1079 [
self setMenu:[aCoder decodeObjectForKey:DEPRECATED_CPPopUpButtonMenuKey]];
1080 [
self setObjectValue:[aCoder decodeObjectForKey:DEPRECATED_CPPopUpButtonSelectedIndexKey]];
1085 [
self addObserver:self forKeyPath:@"menu.items" options:options context:nil];
1086 [
self addObserver:self forKeyPath:@"_firstItem.changeCount" options:options context:nil];
1087 [
self addObserver:self forKeyPath:@"selectedItem.changeCount" options:options context:nil];