From 9796f5d5d7d6d04d4b9425c8098eee2a96afd9d2 Mon Sep 17 00:00:00 2001 From: Francisco Ryan Tolmasky I Date: Mon, 17 Jan 2011 01:31:15 -0800 Subject: [PATCH] Fix CPPopUpButton menu support. - Added enumerateObjectsUsingBlock: to CPArray - Fixed a remaining case of [] being used instead of objectAtIndex: in CPMutableArray. - Added KVO support for menu items array in menus. - Changed CPPopUpButton to use KVO to sync up with menu instead of notifications. - Removed extra menu encoding in CPPopUpButton. - Removed extra mainMenu object from CPApplication. - Use setters in CPResponder instead of direct ivar access in initWithCoder:. Closes #298. Closes $1059. Reviewed by me. --- AppKit/CPApplication.j | 16 +- AppKit/CPMenu/CPMenu.j | 87 +++++--- AppKit/CPMenuItem/CPMenuItem.j | 5 + AppKit/CPPopUpButton.j | 317 +++++++++++++++------------- AppKit/CPResponder.j | 4 +- Foundation/CPArray/CPArray.j | 8 + Foundation/CPArray/CPMutableArray.j | 8 +- Tests/AppKit/CPPopUpButtonTest.j | 71 +++++++ 8 files changed, 325 insertions(+), 191 deletions(-) diff --git a/AppKit/CPApplication.j b/AppKit/CPApplication.j index ec4e0370a..5e00cbddd 100644 --- a/AppKit/CPApplication.j +++ b/AppKit/CPApplication.j @@ -90,7 +90,6 @@ CPRunContinuesResponse = -1002; CPWindow _previousKeyWindow; CPWindow _previousMainWindow; - CPMenu _mainMenu; CPDocumentController _documentController; CPModalSession _currentSession; @@ -530,7 +529,7 @@ CPRunContinuesResponse = -1002; - (BOOL)_handleKeyEquivalent:(CPEvent)anEvent { return [[self keyWindow] performKeyEquivalent:anEvent] || - [_mainMenu performKeyEquivalent:anEvent]; + [[self mainMenu] performKeyEquivalent:anEvent]; } /*! @@ -637,7 +636,7 @@ CPRunContinuesResponse = -1002; */ - (CPMenu)mainMenu { - return _mainMenu; + return [self menu]; } /*! @@ -645,16 +644,21 @@ CPRunContinuesResponse = -1002; @param aMenu the menu to set for the application */ - (void)setMainMenu:(CPMenu)aMenu +{ + [self setMenu:aMenu]; +} + +- (void)setMenu:(CPMenu)aMenu { if ([aMenu _menuName] === "CPMainMenu") { - if (_mainMenu === aMenu) + if ([self menu] === aMenu) return; - _mainMenu = aMenu; + [super setMenu:aMenu]; if ([CPPlatform supportsNativeMainMenu]) - window.cpSetMainMenu(_mainMenu); + window.cpSetMainMenu([self menu]); } else [aMenu _setMenuName:@"CPMainMenu"]; diff --git a/AppKit/CPMenu/CPMenu.j b/AppKit/CPMenu/CPMenu.j index d10312fde..66576e23d 100644 --- a/AppKit/CPMenu/CPMenu.j +++ b/AppKit/CPMenu/CPMenu.j @@ -274,22 +274,7 @@ var _CPMenuBarVisible = NO, */ - (void)insertItem:(CPMenuItem)aMenuItem atIndex:(unsigned)anIndex { - var menu = [aMenuItem menu]; - - if (menu) - if (menu !== self) - [CPException raise:CPInternalInconsistencyException reason:@"Attempted to insert item into menu that was already in another menu."]; - else - return; - - [aMenuItem setMenu:self]; - [_items insertObject:aMenuItem atIndex:anIndex]; - - [[CPNotificationCenter defaultCenter] - postNotificationName:CPMenuDidAddItemNotification - object:self - userInfo:[CPDictionary dictionaryWithObject:anIndex forKey:@"CPMenuItemIndex"]]; - + [self insertObject:aMenuItem inItemsAtIndex:anIndex]; } /*! @@ -346,16 +331,7 @@ var _CPMenuBarVisible = NO, */ - (void)removeItemAtIndex:(unsigned)anIndex { - if (anIndex < 0 || anIndex >= _items.length) - return; - - [_items[anIndex] setMenu:nil]; - [_items removeObjectAtIndex:anIndex]; - - [[CPNotificationCenter defaultCenter] - postNotificationName:CPMenuDidRemoveItemNotification - object:self - userInfo:[CPDictionary dictionaryWithObject:anIndex forKey:@"CPMenuItemIndex"]]; + [self removeObjectFromItemsAtIndex:anIndex]; } /*! @@ -364,9 +340,11 @@ var _CPMenuBarVisible = NO, */ - (void)itemChanged:(CPMenuItem)aMenuItem { - if ([aMenuItem menu] != self) + if ([aMenuItem menu] !== self) return; + [aMenuItem setValue:[aMenuItem valueForKey:@"changeCount"] + 1 forKey:@"changeCount"]; + [[CPNotificationCenter defaultCenter] postNotificationName:CPMenuDidChangeItemNotification object:self @@ -1035,6 +1013,61 @@ var _CPMenuBarVisible = NO, @end +@implementation CPMenu (CPKeyValueCoding) + +- (CPUInteger)countOfItems +{ + return [_items count]; +} + +- (CPMenuItem)objectInItemsAtIndex:(CPUInteger)anIndex +{ + return [_items objectAtIndex:anIndex]; +} + +- (CPArray)itemsAtIndexes:(CPIndexSet)indexes +{ + return [_items objectsAtIndexes:indexes]; +} + +@end + +@implementation CPMenu (CPKeyValueObserving) + +- (void)insertObject:(CPMenuItem)aMenuItem inItemsAtIndex:(CPUInteger)anIndex +{ + var menu = [aMenuItem menu]; + + if (menu) + if (menu !== self) + [CPException raise:CPInternalInconsistencyException reason:@"Attempted to insert item into menu that was already in another menu."]; + else + return; + + [aMenuItem setMenu:self]; + [_items insertObject:aMenuItem atIndex:anIndex]; + + [[CPNotificationCenter defaultCenter] + postNotificationName:CPMenuDidAddItemNotification + object:self + userInfo:[CPDictionary dictionaryWithObject:anIndex forKey:@"CPMenuItemIndex"]]; +} + +- (void)removeObjectFromItemsAtIndex:(CPUInteger)anIndex +{ + if (anIndex < 0 || anIndex >= [_items count]) + return; + + [[_items objectAtIndex:anIndex] setMenu:nil]; + [_items removeObjectAtIndex:anIndex]; + + [[CPNotificationCenter defaultCenter] + postNotificationName:CPMenuDidRemoveItemNotification + object:self + userInfo:[CPDictionary dictionaryWithObject:anIndex forKey:@"CPMenuItemIndex"]]; +} + +@end var CPMenuTitleKey = @"CPMenuTitleKey", CPMenuNameKey = @"CPMenuNameKey", diff --git a/AppKit/CPMenuItem/CPMenuItem.j b/AppKit/CPMenuItem/CPMenuItem.j index 5f34c3367..90c52dd90 100644 --- a/AppKit/CPMenuItem/CPMenuItem.j +++ b/AppKit/CPMenuItem/CPMenuItem.j @@ -77,6 +77,8 @@ id _representedObject; CPView _view; + int _changeCount; + _CPMenuItemView _menuItemView; } @@ -98,12 +100,14 @@ if (self) { + _changeCount = 0; _isSeparator = NO; _title = aTitle; _action = anAction; _isEnabled = YES; + _isHidden = NO; _tag = 0; _state = CPOffState; @@ -890,6 +894,7 @@ var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey", if (self) { + _changeCount = 0; _isSeparator = [aCoder containsValueForKey:CPMenuItemIsSeparatorKey] && [aCoder decodeBoolForKey:CPMenuItemIsSeparatorKey]; _title = [aCoder decodeObjectForKey:CPMenuItemTitleKey]; diff --git a/AppKit/CPPopUpButton.j b/AppKit/CPPopUpButton.j index 6c5b24ec2..d38387beb 100644 --- a/AppKit/CPPopUpButton.j +++ b/AppKit/CPPopUpButton.j @@ -38,10 +38,8 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); */ @implementation CPPopUpButton : CPButton { - int _selectedIndex; + CPUInteger _selectedIndex; CPRectEdge _preferredEdge; - - CPMenu _menu; } + (CPString)defaultThemeClass @@ -76,6 +74,14 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); [self setMenu:[[CPMenu alloc] initWithTitle:@""]]; [self setPullsDown:shouldPullDown]; + + var options = CPKeyValueObservingOptionNew | + CPKeyValueObservingOptionOld;/* | + CPKeyValueObservingOptionInitial; +*/ + [self addObserver:self forKeyPath:@"menu.items" options:options context:nil]; + [self addObserver:self forKeyPath:@"_firstItem.changeCount" options:options context:nil]; + [self addObserver:self forKeyPath:@"selectedItem.changeCount" options:options context:nil]; } return self; @@ -103,7 +109,7 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); if (!changed) return; - var items = [_menu itemArray]; + var items = [[self menu] itemArray]; if ([items count] <= 0) return; @@ -128,7 +134,7 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); */ - (void)addItem:(CPMenuItem)anItem { - [_menu addItem:anItem]; + [[self menu] addItem:anItem]; } /*! @@ -137,7 +143,7 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); */ - (void)addItemWithTitle:(CPString)aTitle { - [_menu addItemWithTitle:aTitle action:NULL keyEquivalent:nil]; + [[self menu] addItemWithTitle:aTitle action:NULL keyEquivalent:nil]; } /*! @@ -167,7 +173,7 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); if ([items[count] title] == aTitle) [self removeItemAtIndex:count]; - [_menu insertItemWithTitle:aTitle action:NULL keyEquivalent:nil atIndex:anIndex]; + [[self menu] insertItemWithTitle:aTitle action:NULL keyEquivalent:nil atIndex:anIndex]; } /*! @@ -175,10 +181,11 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); */ - (void)removeAllItems { - var count = [_menu numberOfItems]; + var menu = [self menu], + count = [menu numberOfItems]; while (count--) - [_menu removeItemAtIndex:0]; + [menu removeItemAtIndex:0]; } /*! @@ -197,7 +204,7 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); */ - (void)removeItemAtIndex:(int)anIndex { - [_menu removeItemAtIndex:anIndex]; + [[self menu] removeItemAtIndex:anIndex]; [self synchronizeTitleAndSelectedItem]; } @@ -207,10 +214,12 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); */ - (CPMenuItem)selectedItem { - if (_selectedIndex < 0 || _selectedIndex > [self numberOfItems] - 1) + var indexOfSelectedItem = [self indexOfSelectedItem]; + + if (indexOfSelectedItem < 0 || indexOfSelectedItem > [self numberOfItems] - 1) return nil; - return [_menu itemAtIndex:_selectedIndex]; + return [[self menu] itemAtIndex:indexOfSelectedItem]; } /*! @@ -270,7 +279,9 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); */ - (void)selectItemAtIndex:(int)anIndex { - if (_selectedIndex == anIndex) + anIndex = +anIndex; + + if (_selectedIndex === anIndex) return; [self willChangeValueForKey:@"selectedIndex"]; @@ -316,74 +327,13 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); } // Getting Menu Items -/*! - Returns the button's menu of items. -*/ -- (CPMenu)menu -{ - return _menu; -} - -/*! - Sets the menu for the button -*/ -- (void)setMenu:(CPMenu)aMenu -{ - if (_menu === aMenu) - return; - - var defaultCenter = [CPNotificationCenter defaultCenter]; - - if (_menu) - { - [defaultCenter - removeObserver:self - name:CPMenuDidAddItemNotification - object:_menu]; - - [defaultCenter - removeObserver:self - name:CPMenuDidChangeItemNotification - object:_menu]; - - [defaultCenter - removeObserver:self - name:CPMenuDidRemoveItemNotification - object:_menu]; - } - - _menu = aMenu; - - if (_menu) - { - [defaultCenter - addObserver:self - selector:@selector(menuDidAddItem:) - name:CPMenuDidAddItemNotification - object:_menu]; - - [defaultCenter - addObserver:self - selector:@selector(menuDidChangeItem:) - name:CPMenuDidChangeItemNotification - object:_menu]; - - [defaultCenter - addObserver:self - selector:@selector(menuDidRemoveItem:) - name:CPMenuDidRemoveItemNotification - object:_menu]; - } - - [self synchronizeTitleAndSelectedItem]; -} /*! Returns a count of the number of items in the button's menu. */ - (int)numberOfItems { - return [_menu numberOfItems]; + return [[self menu] numberOfItems]; } /*! @@ -391,7 +341,7 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); */ - (CPArray)itemArray { - return [_menu itemArray]; + return [[self menu] itemArray]; } /*! @@ -400,7 +350,7 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); */ - (CPMenuItem)itemAtIndex:(unsigned)anIndex { - return [_menu itemAtIndex:anIndex]; + return [[self menu] itemAtIndex:anIndex]; } /*! @@ -409,7 +359,7 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); */ - (CPString)itemTitleAtIndex:(unsigned)anIndex { - return [[_menu itemAtIndex:anIndex] title]; + return [[[self menu] itemAtIndex:anIndex] title]; } /*! @@ -434,7 +384,9 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); */ - (CPMenuItem)itemWithTitle:(CPString)aTitle { - return [_menu itemAtIndex:[_menu indexOfItemWithTitle:aTitle]]; + var menu = [self menu]; + + return [menu itemAtIndex:[menu indexOfItemWithTitle:aTitle]]; } /*! @@ -442,7 +394,7 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); */ - (CPMenuItem)lastItem { - return [[_menu itemArray] lastObject]; + return [[[self menu] itemArray] lastObject]; } // Getting the Indices of Menu Items @@ -452,7 +404,7 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); */ - (int)indexOfItem:(CPMenuItem)aMenuItem { - return [_menu indexOfItem:aMenuItem]; + return [[self menu] indexOfItem:aMenuItem]; } /*! @@ -461,7 +413,7 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); */ - (int)indexOfItemWithTag:(int)aTag { - return [_menu indexOfItemWithTag:aTag]; + return [[self menu] indexOfItemWithTag:aTag]; } /*! @@ -470,7 +422,7 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); */ - (int)indexOfItemWithTitle:(CPString)aTitle { - return [_menu indexOfItemWithTitle:aTitle]; + return [[self menu] indexOfItemWithTitle:aTitle]; } /*! @@ -481,7 +433,7 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); */ - (int)indexOfItemWithRepresentedObject:(id)anObject { - return [_menu indexOfItemWithRepresentedObject:anObject]; + return [[self menu] indexOfItemWithRepresentedObject:anObject]; } /*! @@ -493,7 +445,7 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); */ - (int)indexOfItemWithTarget:(id)aTarget action:(SEL)anAction { - return [_menu indexOfItemWithTarget:aTarget action:anAction]; + return [[self menu] indexOfItemWithTarget:aTarget action:anAction]; } // Setting the Cell Edge to Pop out in Restricted Situations @@ -529,7 +481,7 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); if ([self pullsDown]) { - var items = [_menu itemArray]; + var items = [[self menu] itemArray]; if ([items count] <= 0) [self addItemWithTitle:aTitle]; @@ -577,7 +529,7 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); if ([self pullsDown]) { - var items = [_menu itemArray]; + var items = [[self menu] itemArray]; if ([items count] > 0) item = items[0]; @@ -589,73 +541,117 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); [super setTitle:[item title]]; } -// -/*! - Called when the menu has a new item added to it. - @param aNotification information about the event -*/ -- (void)menuDidAddItem:(CPNotification)aNotification +- (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)aContext { - var index = [[aNotification userInfo] objectForKey:@"CPMenuItemIndex"]; + var pullsDown = [self pullsDown]; - if (_selectedIndex < 0) - [self selectItemAtIndex:0]; - - else if (index == _selectedIndex) + if (!pullsDown && aKeyPath === @"selectedItem.changeCount" || + pullsDown && (aKeyPath === @"_firstItem" || aKeyPath === @"_firstItem.changeCount")) [self synchronizeTitleAndSelectedItem]; - else if (index < _selectedIndex) - ++_selectedIndex; - - if (index == 0 && [self pullsDown]) + // FIXME: This is due to a bug in KVO, we should never get it for "menu". + if (aKeyPath === @"menu") { - var items = [_menu itemArray]; + aKeyPath = @"menu.items"; - [items[0] setHidden:YES]; - - if (items.length > 0) - [items[1] setHidden:NO]; + [changes setObject:CPKeyValueChangeSetting forKey:CPKeyValueChangeKindKey]; + [changes setObject:[[self menu] itemArray] forKey:CPKeyValueChangeNewKey]; } - var item = [_menu itemArray][index], - action = [item action]; - - if (!action || (action === @selector(_popUpItemAction:))) + if (aKeyPath === @"menu.items") { - [item setTarget:self]; - [item setAction:@selector(_popUpItemAction:)]; + var changeKind = [changes objectForKey:CPKeyValueChangeKindKey], + indexOfSelectedItem = [self indexOfSelectedItem]; + + if (changeKind === CPKeyValueChangeRemoval) + { + var index = CPNotFound, + indexes = [changes objectForKey:CPKeyValueChangeIndexesKey]; + + if ([indexes containsIndex:0] && [self pullsDown]) + [self _firstItemDidChange]; + + // See whether the index has changed, despite the actual item not changing. + while ((index = [indexes indexGreaterThanIndex:index]) !== CPNotFound && + index <= indexOfSelectedItem) + --indexOfSelectedItem; + + [self selectItemAtIndex:indexOfSelectedItem]; + } + + else if (changeKind === CPKeyValueChangeReplacement) + { + var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey]; + + if (pullsDown && [indexes containsIndex:0] || + !pullsDown && [indexes containsIndex:indexOfSelectedItem]) + [self synchronizeTitleAndSelectedItem]; + } + + else + { + // No matter what, we want to prepare the new items. + var newItems = [changes objectForKey:CPKeyValueChangeNewKey]; + + [newItems enumerateObjectsUsingBlock:function(aMenuItem) + { + var action = [aMenuItem action]; + + if (!action) + [aMenuItem setAction:action = @selector(_popUpItemAction:)]; + + if (action === @selector(_popUpItemAction:)) + [aMenuItem setTarget:self]; + }]; + + if (changeKind === CPKeyValueChangeSetting) + { + [self _firstItemDidChange]; + + _selectedIndex = -2; + [self selectItemAtIndex:MIN([newItems count] - 1, indexOfSelectedItem)]; + } + + else //if (changeKind === CPKeyValueChangeInsertion) + { + var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey]; + + if ([self pullsDown] && [indexes containsIndex:0]) + { + [self _firstItemDidChange]; + + if ([self numberOfItems] > 1) + { + var index = CPNotFound, + originalIndex = 0; + + while ((index = [indexes indexGreaterThanIndex:index]) !== CPNotFound && + index <= originalIndex) + ++originalIndex; + + [[self itemAtIndex:originalIndex] setHidden:NO]; + } + } + + if (indexOfSelectedItem < 0) + [self selectItemAtIndex:0]; + + else + { + var index = CPNotFound; + + // See whether the index has changed, despite the actual item not changing. + while ((index = [indexes indexGreaterThanIndex:index]) !== CPNotFound && + index <= indexOfSelectedItem) + ++indexOfSelectedItem; + + [self selectItemAtIndex:indexOfSelectedItem]; + } + } + } } -} -/*! - Called when a menu item has changed. - @param aNotification information about the event -*/ -- (void)menuDidChangeItem:(CPNotification)aNotification -{ - var index = [[aNotification userInfo] objectForKey:@"CPMenuItemIndex"]; - - if ([self pullsDown] && index != 0) - return; - - if (![self pullsDown] && index != _selectedIndex) - return; - - [self synchronizeTitleAndSelectedItem]; -} - -/*! - Called when an item was removed from the menu. - @param aNotification information about the event -*/ -- (void)menuDidRemoveItem:(CPNotification)aNotification -{ - var numberOfItems = [self numberOfItems]; - - if (numberOfItems <= _selectedIndex && numberOfItems > 0) - [self selectItemAtIndex:numberOfItems - 1]; - else - [self synchronizeTitleAndSelectedItem]; +// [super observeValueForKeyPath:aKeyPath ofObject:anObject change:changes context:aContext]; } - (void)mouseDown:(CPEvent)anEvent @@ -735,6 +731,22 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); [self sendAction:[self action] to:[self target]]; } +- (void)_firstItemDidChange +{ + [self willChangeValueForKey:@"_firstItem"]; + [self didChangeValueForKey:@"_firstItem"]; + + [[self _firstItem] setHidden:YES]; +} + +- (CPMenuItem)_firstItem +{ + if ([self numberOfItems] <= 0) + return nil; + + return [[self menu] itemAtIndex:0]; +} + - (void)takeValueFromKeyPath:(CPString)aKeyPath ofObjects:(CPArray)objects { var count = objects.length, @@ -744,18 +756,13 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); [self setEnabled:YES]; while (count-- > 1) - { if (value !== [objects[count] valueForKeyPath:aKeyPath]) - { [[self selectedItem] setState:CPOffState]; - } - } } @end -var CPPopUpButtonMenuKey = @"CPPopUpButtonMenuKey", - CPPopUpButtonSelectedIndexKey = @"CPPopUpButtonSelectedIndexKey", +var CPPopUpButtonSelectedIndexKey = @"CPPopUpButtonSelectedIndexKey", CPPopUpButtonPullsDownKey = @"CPPopUpButtonPullsDownKey"; @implementation CPPopUpButton (CPCoding) @@ -773,10 +780,17 @@ var CPPopUpButtonMenuKey = @"CPPopUpButtonMenuKey", if (self) { // Nothing is currently selected - _selectedIndex = -1; + _selectedIndex = CPNotFound; - [self setMenu:[aCoder decodeObjectForKey:CPPopUpButtonMenuKey]]; [self selectItemAtIndex:[aCoder decodeObjectForKey:CPPopUpButtonSelectedIndexKey]]; + + var options = CPKeyValueObservingOptionNew | + CPKeyValueObservingOptionOld;/* | + CPKeyValueObservingOptionInitial; +*/ + [self addObserver:self forKeyPath:@"menu.items" options:options context:nil]; + [self addObserver:self forKeyPath:@"_firstItem.changeCount" options:options context:nil]; + [self addObserver:self forKeyPath:@"selectedItem.changeCount" options:options context:nil]; } return self; @@ -791,7 +805,6 @@ var CPPopUpButtonMenuKey = @"CPPopUpButtonMenuKey", { [super encodeWithCoder:aCoder]; - [aCoder encodeObject:_menu forKey:CPPopUpButtonMenuKey]; [aCoder encodeInt:_selectedIndex forKey:CPPopUpButtonSelectedIndexKey]; } diff --git a/AppKit/CPResponder.j b/AppKit/CPResponder.j index 244244103..8890dc63e 100644 --- a/AppKit/CPResponder.j +++ b/AppKit/CPResponder.j @@ -358,8 +358,8 @@ var CPResponderNextResponderKey = @"CPResponderNextResponderKey", if (self) { - _nextResponder = [aCoder decodeObjectForKey:CPResponderNextResponderKey]; - _menu = [aCoder decodeObjectForKey:CPResponderMenuKey]; + [self setNextResponder:[aCoder decodeObjectForKey:CPResponderNextResponderKey]]; + [self setMenu:[aCoder decodeObjectForKey:CPResponderMenuKey]]; } return self; diff --git a/Foundation/CPArray/CPArray.j b/Foundation/CPArray/CPArray.j index d690e0801..da694c7ff 100755 --- a/Foundation/CPArray/CPArray.j +++ b/Foundation/CPArray/CPArray.j @@ -517,6 +517,14 @@ var concat = Array.prototype.concat, objj_msgSend([self objectAtIndex:index], aSelector); } +- (void)enumerateObjectsUsingBlock:(Function)aFunction +{ + var index = 0, + count = [self count]; + + for (; index < count; ++index) + aFunction([self objectAtIndex:index], index); +} // Comparing arrays /*! diff --git a/Foundation/CPArray/CPMutableArray.j b/Foundation/CPArray/CPMutableArray.j index 374445a65..2e06fab2d 100644 --- a/Foundation/CPArray/CPMutableArray.j +++ b/Foundation/CPArray/CPMutableArray.j @@ -136,15 +136,15 @@ @param anIndexSet the set of indices to array positions that will be replaced @param objects the array of objects to place in the specified indices */ -- (void)replaceObjectsAtIndexes:(CPIndexSet)anIndexSet withObjects:(CPArray)objects +- (void)replaceObjectsAtIndexes:(CPIndexSet)indexes withObjects:(CPArray)objects { var i = 0, - index = [anIndexSet firstIndex]; + index = [indexes firstIndex]; while (index !== CPNotFound) { - [self replaceObjectAtIndex:index withObject:objects[i++]]; - index = [anIndexSet indexGreaterThanIndex:index]; + [self replaceObjectAtIndex:index withObject:[objects objectAtIndex:i++]]; + index = [indexes indexGreaterThanIndex:index]; } } diff --git a/Tests/AppKit/CPPopUpButtonTest.j b/Tests/AppKit/CPPopUpButtonTest.j index 7ab2e8471..e102b5a40 100644 --- a/Tests/AppKit/CPPopUpButtonTest.j +++ b/Tests/AppKit/CPPopUpButtonTest.j @@ -12,6 +12,77 @@ button = [CPPopUpButton new]; } +- (void)testMenuSynchronization +{ + var popUpButton = [[CPPopUpButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 28.0) pullsDown:NO]; + + [self assert:CPNotFound equals:[popUpButton indexOfSelectedItem]]; + + [popUpButton addItemWithTitle:@"one"]; + + [self assert:0 equals:[popUpButton indexOfSelectedItem]]; + + [popUpButton addItemWithTitle:@"two"]; + [popUpButton addItemWithTitle:@"three"]; + [popUpButton addItemWithTitle:@"four"]; + [popUpButton addItemWithTitle:@"five"]; + [popUpButton addItemWithTitle:@"six"]; + + [self assert:0 equals:[popUpButton indexOfSelectedItem]]; + + [popUpButton insertItemWithTitle:@"negative one" atIndex:0]; + + [self assert:1 equals:[popUpButton indexOfSelectedItem]]; + + var items = [ + [[CPMenuItem alloc] initWithTitle:@"negative five" action:nil keyEquivalent:@""], + [[CPMenuItem alloc] initWithTitle:@"negative four" action:nil keyEquivalent:@""], + [[CPMenuItem alloc] initWithTitle:@"negative three" action:nil keyEquivalent:@""], + [[CPMenuItem alloc] initWithTitle:@"negative two" action:nil keyEquivalent:@""] + ], + indexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, 4)], + mutableItemsArray = [[popUpButton menu] mutableArrayValueForKey:@"items"]; + + [mutableItemsArray insertObjects:items atIndexes:indexes]; + + [self assert:5 equals:[popUpButton indexOfSelectedItem]]; + + [[popUpButton menu] removeItemAtIndex:5]; + + [self assert:4 equals:[popUpButton indexOfSelectedItem]]; + + [[popUpButton menu] removeItemAtIndex:0]; + + [self assert:3 equals:[popUpButton indexOfSelectedItem]]; + + [popUpButton selectItemAtIndex:1]; + + indexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(1, 3)]; + [mutableItemsArray removeObjectsAtIndexes:indexes]; + + [self assert:0 equals:[popUpButton indexOfSelectedItem]]; + + var pullDownButton = [[CPPopUpButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 28.0) pullsDown:YES]; + + [pullDownButton addItemWithTitle:@"First Item"]; + + [self assert:YES equals:[[pullDownButton itemAtIndex:0] isHidden]]; + + [pullDownButton addItemWithTitle:@"Second Item"]; + + [self assert:YES equals:[[pullDownButton itemAtIndex:0] isHidden]]; + [self assert:NO equals:[[pullDownButton itemAtIndex:1] isHidden]]; + + [pullDownButton removeItemAtIndex:0]; + + [self assert:YES equals:[[pullDownButton itemAtIndex:0] isHidden]]; + + [pullDownButton insertItemWithTitle:@"A Title" atIndex:0]; + + [self assert:YES equals:[[pullDownButton itemAtIndex:0] isHidden]]; + [self assert:NO equals:[[pullDownButton itemAtIndex:1] isHidden]]; +} + - (void)testItemTitles { [self assert:[] equals:[button itemTitles]];