/** NSPopUpButtonCell Copyright (C) 1999 Free Software Foundation, Inc. Author: Fred Kiefer Date: Jul 2003 Author: Michael Hanni Date: 1999 This file is part of the GNUstep GUI Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; see the file COPYING.LIB. If not, see or write to the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "config.h" #import #import #import "AppKit/NSApplication.h" #import "AppKit/NSEvent.h" #import "AppKit/NSGraphics.h" #import "AppKit/NSImage.h" #import "AppKit/NSMenu.h" #import "AppKit/NSMenuView.h" #import "AppKit/NSPopUpButton.h" #import "AppKit/NSPopUpButtonCell.h" #import "AppKit/NSWindow.h" #import "GNUstepGUI/GSTheme.h" /* The image to use in a specific popupbutton depends on type and * preferred edge; that is, _pbc_image[0] if it is a * popup menu, _pbc_image[1] if it is a pulls down list pointing down, * and so on. */ static NSImage *_pbc_image[5]; @interface NSPopUpButtonCell (CocoaExtensions) - (void) _popUpItemAction: (id)sender; @end @implementation NSPopUpButtonCell + (void) initialize { if (self == [NSPopUpButtonCell class]) { [self setVersion: 3]; ASSIGN(_pbc_image[0], [NSImage imageNamed: @"common_Nibble"]); ASSIGN(_pbc_image[1], [NSImage imageNamed: @"common_3DArrowDown"]); ASSIGN(_pbc_image[2], [NSImage imageNamed: @"common_3DArrowRight"]); ASSIGN(_pbc_image[3], [NSImage imageNamed: @"common_3DArrowUp"]); ASSIGN(_pbc_image[4], [NSImage imageNamed: @"common_3DArrowLeft"]); } } + (BOOL) prefersTrackingUntilMouseUp { return YES; } + (NSFocusRingType) defaultFocusRingType { return NSFocusRingTypeDefault; } // Initialization /** * Initialize a blank cell. */ - (id) init { return [self initTextCell: @"" pullsDown: NO]; } /** * Initialize with stringValue. */ - (id) initTextCell: (NSString *)stringValue { return [self initTextCell: stringValue pullsDown: NO]; } /* * Helper method should be overriden by Gorm */ - (void)_initMenu { NSMenu *menu; menu = [[NSMenu alloc] initWithTitle: @""]; [self setMenu: menu]; RELEASE(menu); } /** * Initialize with stringValue and pullDown. If pullDown is YES, the * reciever will be a pulldown button. */ - (id) initTextCell: (NSString *)stringValue pullsDown: (BOOL)flag { self = [super initTextCell: stringValue]; if (!self) return nil; [self _initMenu]; [self setPullsDown: flag]; _pbcFlags.usesItemFromMenu = YES; [self setPreferredEdge: NSMinYEdge]; [self setArrowPosition: NSPopUpArrowAtCenter]; if ([stringValue length] > 0) { [self addItemWithTitle: stringValue]; } return self; } - (void) dealloc { /* * The popup must be closed here, just in case the cell goes away * while the popup is still displayed. In that case the notification * center would still send notifications to the deallocated cell. */ if ([[_menu window] isVisible]) { [self dismissPopUp]; } /* * We don't use methods here to clean up the selected item, the menu * item and the menu as these methods internally update the menu, * which tries to access the target of the menu item (or of this cell). * When the popup is relases this target may already have been freed, * so the local reference to it is invalid and will result in a * segmentation fault. */ _selectedItem = nil; [super dealloc]; } // Getters and setters. /** * Set the menu for the popup. */ - (void) setMenu: (NSMenu *)menu { if (_menu == menu) { return; } if (_menu != nil) { [_menu _setOwnedByPopUp: nil]; } ASSIGN(_menu, menu); if (_menu != nil) { [_menu _setOwnedByPopUp: self]; /* We need to set the menu view so we trigger the special case * popupbutton code in super class NSMenuItemCell */ [self setMenuView: [_menu menuRepresentation]]; } else { [self setMenuView: nil]; } [self selectItemAtIndex: [_menu numberOfItems] - 1]; [self synchronizeTitleAndSelectedItem]; } /** * Return the menu for the popup. */ - (NSMenu *) menu { return _menu; } /** * Returns YES, if this is a pull-down */ - (void) setPullsDown: (BOOL)flag { NSMenuItem *item = _menuItem; [self setMenuItem: nil]; if (flag && !_pbcFlags.pullsDown && _pbcFlags.altersStateOfSelectedItem) { [[self selectedItem] setState: NSOffState]; } _pbcFlags.pullsDown = flag; [self setMenuItem: item]; } /** * Returns YES, if this is a pull-down */ - (BOOL) pullsDown { return _pbcFlags.pullsDown; } /** * Set to YES, if the items are to be autoenabled. */ - (void) setAutoenablesItems: (BOOL)flag { [_menu setAutoenablesItems: flag]; } /** * Returns YES, if the items are autoenabled. */ - (BOOL) autoenablesItems { return [_menu autoenablesItems]; } /** * Set the preferred edge as described by edge. This is used * to determine the edge which will open the popup when the screen * is small. */ - (void) setPreferredEdge: (NSRectEdge)preferredEdge { _pbcFlags.preferredEdge = preferredEdge; } /** * Return the preferred edge. */ - (NSRectEdge) preferredEdge { return _pbcFlags.preferredEdge; } /** * Set to YES, if the reciever should use a menu item for its title. YES * is the default. */ - (void) setUsesItemFromMenu: (BOOL)flag { if (_pbcFlags.usesItemFromMenu != flag) { _pbcFlags.usesItemFromMenu = flag; if (!flag) { NSMenuItem *anItem; anItem = [[NSMenuItem alloc] initWithTitle: [self title] action: NULL keyEquivalent: nil]; [self setMenuItem: anItem]; RELEASE(anItem); } } } /** * Returns YES, if the reciever uses a menu item for its title. */ - (BOOL) usesItemFromMenu { return _pbcFlags.usesItemFromMenu; } /** * Return YES, if the reciever changes the state of the item chosen by * the user. */ - (void) setAltersStateOfSelectedItem: (BOOL)flag { id selectedItem = [self selectedItem]; if (flag) { [selectedItem setState: NSOffState]; } else { [selectedItem setState: NSOnState]; } _pbcFlags.altersStateOfSelectedItem = flag; } /** * Return YES, if the reciever changes the state of the item chosen by * the user. */ - (BOOL) altersStateOfSelectedItem { return _pbcFlags.altersStateOfSelectedItem; } /** * Returns the current arrow position of the reciever. */ - (NSPopUpArrowPosition) arrowPosition { return _pbcFlags.arrowPosition; } /** * Sets the current arrow position of the reciever. */ - (void) setArrowPosition: (NSPopUpArrowPosition)pos { _pbcFlags.arrowPosition = pos; } // Item management /** * Add an item to the popup with title. */ - (void) addItemWithTitle: (NSString *)title { [self insertItemWithTitle: title atIndex: [_menu numberOfItems]]; } /** * Add a number of items to the reciever using the provided itemTitles array. */ - (void) addItemsWithTitles: (NSArray *)titles { unsigned c = [titles count]; unsigned i; for (i = 0; i < c; i++) { [self addItemWithTitle: [titles objectAtIndex: i]]; } } /** * Adds an item with the given title at index. If an item already exists at * index, it, and all items after it are advanced one position. Index needs * to be within the valid range for the array of items in the popup button. */ - (void) insertItemWithTitle: (NSString *)title atIndex: (int)index { id anItem; int i, count; i = [self indexOfItemWithTitle: title]; if (-1 != i) { [self removeItemAtIndex: i]; } count = [_menu numberOfItems]; if (index < 0) index = 0; if (index > count) index = count; anItem = [_menu insertItemWithTitle: title action: NULL keyEquivalent: @"" atIndex: index]; /* Disable showing the On/Off/Mixed state. We change the state of menu items when selected, according to the doc, but we don't want it to appear on the screen. */ [anItem setOnStateImage: nil]; [anItem setMixedStateImage: nil]; // FIXME: The documentation is unclear what to set here. //[anItem setAction: [self action]]; //[anItem setTarget: [self target]]; // Or //[anItem setAction: @selector(_popUpItemAction:)]; //[anItem setTarget: self]; // Select the new item if there isn't any selection. if (_selectedItem == nil) { [self selectItem: anItem]; } } /** * Remove a given item based on its title. */ - (void) removeItemWithTitle: (NSString *)title { [self removeItemAtIndex: [self indexOfItemWithTitle: title]]; } /** * Remove a given item based on its index, must be a valid index within the * range for the item array of this popup. */ - (void) removeItemAtIndex: (int)index { if (index == [self indexOfSelectedItem]) { [self selectItem: nil]; } [_menu removeItemAtIndex: index]; } /** * Purges all items from the popup. */ - (void) removeAllItems { [self selectItem: nil]; while ([_menu numberOfItems] > 0) { [_menu removeItemAtIndex: 0]; } } // Referencing items... /** * Item array of the reciever. */ - (NSArray *) itemArray { return [_menu itemArray]; } /** * Number of items in the reciever. */ - (int) numberOfItems { return [_menu numberOfItems]; } /** * Return the index of item in the item array of the reciever. */ - (int) indexOfItem: (id )item { return [_menu indexOfItem: item]; } /** * Return index of the item with the given title. */ - (int) indexOfItemWithTitle: (NSString *)title { return [_menu indexOfItemWithTitle: title]; } /** * Return index of the item with a tag equal to aTag. */ - (int) indexOfItemWithTag: (int)tag { return [_menu indexOfItemWithTag: tag]; } /** * Index of the item whose menu item's representedObject is equal to obj. */ - (int) indexOfItemWithRepresentedObject: (id)obj { return [_menu indexOfItemWithRepresentedObject: obj]; } /** * Index of the item in the reciever whose target and action * are equal to aTarget and actionSelector. */ - (int) indexOfItemWithTarget: (id)aTarget andAction: (SEL)actionSelector { return [_menu indexOfItemWithTarget: aTarget andAction: actionSelector]; } /** * Return the item at index. */ - (id ) itemAtIndex: (int)index { if ((index >= 0) && (index < [_menu numberOfItems])) { return [_menu itemAtIndex: index]; } else { return nil; } } /** * Return the item with title. */ - (id ) itemWithTitle: (NSString *)title { return [_menu itemWithTitle: title]; } /** * Return the item listed last in the reciever. */ - (id ) lastItem { int end = [_menu numberOfItems] - 1; if (end < 0) return nil; return [_menu itemAtIndex: end]; } /* The Cocoa specification has this method as: - (id ) objectValue But this gives a conflict with the NSCell declaration of this method, which is: - (id) objectValue */ - (id ) objectValue { return [NSNumber numberWithInt: [self indexOfSelectedItem]]; } - (void) setObjectValue: (id)object { if ([object respondsToSelector: @selector(intValue)]) { int i = [object intValue]; [self selectItemAtIndex: i]; [self synchronizeTitleAndSelectedItem]; } } - (NSImage *) _currentArrowImage { if (_pbcFlags.pullsDown) { if (_pbcFlags.arrowPosition == NSPopUpNoArrow) { return nil; } if (_pbcFlags.preferredEdge == NSMinYEdge) { return _pbc_image[1]; } else if (_pbcFlags.preferredEdge == NSMaxXEdge) { return _pbc_image[2]; } else if (_pbcFlags.preferredEdge == NSMaxYEdge) { return _pbc_image[3]; } else if (_pbcFlags.preferredEdge == NSMinXEdge) { return _pbc_image[4]; } else { return _pbc_image[1]; } } else { return _pbc_image[0]; } } - (void) setImage: (NSImage *)anImage { // Do nothing as the image is determined by the current item } - (void) setMenuItem: (NSMenuItem *)item { NSImage *image; if (_menuItem == item) return; image = [self _currentArrowImage]; if ([_menuItem image] == image) { [_menuItem setImage: nil]; } //[super setMenuItem: item]; ASSIGN(_menuItem, item); if ([_menuItem image] == nil) { [_menuItem setImage: image]; } } - (void) selectItem: (id )item { id oldSelectedItem = _selectedItem; if (_selectedItem == item) return; if (_selectedItem != nil) { if (_pbcFlags.altersStateOfSelectedItem) { [_selectedItem setState: NSOffState]; } } _selectedItem = item; if (_selectedItem != nil) { if (_pbcFlags.altersStateOfSelectedItem) { [_selectedItem setState: NSOnState]; } } /* Set the item in the menu */ [[_menu menuRepresentation] setHighlightedItemIndex: [_menu indexOfItem: _selectedItem]]; [[_menu menuRepresentation] setNeedsDisplayForItemAtIndex: [_menu indexOfItem: oldSelectedItem]]; } - (void) selectItemAtIndex: (int)index { id anItem; if (index < 0) anItem = nil; else anItem = [self itemAtIndex: index]; [self selectItem: anItem]; } - (void) selectItemWithTitle: (NSString *)title { id anItem = [self itemWithTitle: title]; [self selectItem: anItem]; } - (void) setTitle: (NSString *)aString { id anItem; if (!_pbcFlags.usesItemFromMenu) { [_menuItem setTitle: aString]; return; } else if (_pbcFlags.pullsDown) { if ([_menu numberOfItems] == 0) { anItem = nil; } else { anItem = [_menu itemAtIndex: 0]; } } else { anItem = [_menu itemWithTitle: aString]; if (anItem == nil) { [self addItemWithTitle: aString]; anItem = [_menu itemWithTitle: aString]; } } [self selectItem: anItem]; } - (NSString *)stringValue { return [self titleOfSelectedItem]; } - (id ) selectedItem { return _selectedItem; } - (int) indexOfSelectedItem { return [_menu indexOfItem: [self selectedItem]]; } - (void) synchronizeTitleAndSelectedItem { int index; if (!_pbcFlags.usesItemFromMenu) return; if ([_menu numberOfItems] == 0) { index = -1; } else if (_pbcFlags.pullsDown) { index = 0; } else { index = [[_menu menuRepresentation] highlightedItemIndex]; if (index < 0) { // If no item is highighted, display the selected one, if there is one. index = [self indexOfSelectedItem]; } else { // Selected the highlighted item [self selectItemAtIndex: index]; } } if ((index >= 0) && ([_menu numberOfItems] > index)) { NSMenuItem *anItem; // This conversion is needed as [setMenuItem:] expects an NSMenuItem anItem = (NSMenuItem *)[_menu itemAtIndex: index]; [self setMenuItem: anItem]; } else { [self setMenuItem: nil]; } if (_control_view) if ([_control_view isKindOfClass: [NSControl class]]) [(NSControl *)_control_view updateCell: self]; } // Title management /** * Set item title at the given index in the reciever. */ - (NSString *) itemTitleAtIndex: (int)index { return [[self itemAtIndex: index] title]; } /** * Returns an array containing all of the current item titles. */ - (NSArray *) itemTitles { unsigned count = [_menu numberOfItems]; id items[count]; unsigned i; [[_menu itemArray] getObjects: items]; for (i = 0; i < count; i++) { items[i] = [items[i] title]; } return [NSArray arrayWithObjects: items count: count]; } /** * Returns the title of the currently selected item in the reciever. */ - (NSString *) titleOfSelectedItem { id item = [self selectedItem]; if (item != nil) return [item title]; else return @""; } /** * Attach popup */ - (void) attachPopUpWithFrame: (NSRect)cellFrame inView: (NSView *)controlView { NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; NSWindow *cvWin = [controlView window]; NSMenuView *mr = [_menu menuRepresentation]; int selectedItem; [nc postNotificationName: NSPopUpButtonCellWillPopUpNotification object: self]; [nc postNotificationName: NSPopUpButtonWillPopUpNotification object: controlView]; // Convert to Screen Coordinates cellFrame = [controlView convertRect: cellFrame toView: nil]; cellFrame.origin = [cvWin convertBaseToScreen: cellFrame.origin]; if (_pbcFlags.pullsDown) selectedItem = -1; else selectedItem = [self indexOfSelectedItem]; if (selectedItem > 0) { [mr setHighlightedItemIndex: selectedItem]; } // display the menu item... [[GSTheme theme] displayPopUpMenu: mr withCellFrame: cellFrame controlViewWindow: cvWin preferredEdge: _pbcFlags.preferredEdge selectedItem: selectedItem]; [nc addObserver: self selector: @selector(_handleNotification:) name: NSMenuDidSendActionNotification object: _menu]; } /** * Dismiss the reciever. */ - (void) dismissPopUp { NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; NSMenuView *mr = [_menu menuRepresentation]; NSWindow *mWin; [nc removeObserver: self name: NSMenuDidSendActionNotification object: _menu]; [_menu close]; // remove from main window mWin = [mr window]; [[mWin parentWindow] removeChildWindow: mWin]; } /* Private method handles all cases after doing a selection from the popup menu. Especially the obscure case where the user uses the keyboard to open a popup, but subsequently uses the mouse to select an item. We'll never know this was done (and thus cannot dismiss the popUp) without getting this notification */ - (void) _handleNotification: (NSNotification*)aNotification { NSString *name = [aNotification name]; if ([name isEqual: NSMenuDidSendActionNotification] == YES) { [self dismissPopUp]; [self synchronizeTitleAndSelectedItem]; } } - (BOOL) trackMouse: (NSEvent *)theEvent inRect: (NSRect)cellFrame ofView: (NSView *)controlView untilMouseUp: (BOOL)untilMouseUp { NSMenuView *mr = [[self menu] menuRepresentation]; NSWindow *menuWindow = [mr window]; NSEvent *e; NSPoint p; if ([self isEnabled] == NO) return NO; if ([[self menu] numberOfItems] == 0) { NSBeep (); return NO; } // Attach the popUp [self attachPopUpWithFrame: cellFrame inView: controlView]; p = [[controlView window] convertBaseToScreen: [theEvent locationInWindow]]; p = [menuWindow convertScreenToBase: p]; // Process events; we start menu events processing by converting // this event to the menu window, and sending it there. e = [NSEvent mouseEventWithType: [theEvent type] location: p modifierFlags: [theEvent modifierFlags] timestamp: [theEvent timestamp] windowNumber: [menuWindow windowNumber] context: [theEvent context] eventNumber: [theEvent eventNumber] clickCount: [theEvent clickCount] pressure: [theEvent pressure]]; // Send the event directly to the popup window, as it may not be located // at the event position. [mr mouseDown: e]; // End of mouse tracking here -- dismiss popup // No synchronization needed here if ([[_menu window] isVisible]) { [self dismissPopUp]; return NO; } return YES; } /** * Perform the click operation with the given frame and controlView. */ - (void) performClickWithFrame: (NSRect)frame inView: (NSView *)controlView { [super performClickWithFrame: frame inView: controlView]; [self attachPopUpWithFrame: frame inView: controlView]; } /* * This drawing uses the same code that is used to draw cells in the menu. */ - (void) drawInteriorWithFrame: (NSRect)cellFrame inView: (NSView*)controlView { BOOL new = NO; if ([self menuItem] == nil) { NSMenuItem *anItem; /* * Create a temporary NSMenuItemCell to at least draw our control, * if items array is empty. */ anItem = [NSMenuItem new]; [anItem setTitle: [self title]]; /* We need this menu item because NSMenuItemCell gets its contents * from the menuItem not from what is set in the cell */ [self setMenuItem: anItem]; RELEASE(anItem); new = YES; } /* We need to calc our size to get images placed correctly */ [self calcSize]; [super drawInteriorWithFrame: cellFrame inView: controlView]; /* Unset the item to restore balance if a new was created */ if (new) { [self setMenuItem: nil]; } } /* FIXME: this method needs to be rewritten to be something like * NSMenuView's sizeToFit. That way if you call [NSPopUpButton sizeToFit]; * you will get the absolutely correct cellSize. * Not sure if this is true. Maybe the popup should only use the size * of the current title, at least, when usesItemFromMenu is false. */ - (NSSize) cellSize { NSSize s; NSSize imageSize; NSSize titleSize; int i, count; NSString *title; NSImage *image; count = [_menu numberOfItems]; image = [self _currentArrowImage]; if (image) imageSize = [image size]; else imageSize = NSZeroSize; s = NSMakeSize(0, imageSize.height); if (count == 0) { title = [self title]; titleSize = [self _sizeText: title]; if (titleSize.width > s.width) s.width = titleSize.width; if (titleSize.height > s.height) s.height = titleSize.height; } for (i = 0; i < count; i++) { title = [[_menu itemAtIndex: i] title]; titleSize = [self _sizeText: title]; if (titleSize.width > s.width) s.width = titleSize.width; if (titleSize.height > s.height) s.height = titleSize.height; } s.width += imageSize.width; s.width += 5; /* Left border to text (border included) */ s.width += 3; /* Text to Image */ s.width += 4; /* Right border to image (border included) */ /* (vertical) border: */ s.height += 2 * [[GSTheme theme] sizeForBorderType: NSBezelBorder].height; /* Spacing between border and inside: */ s.height += 2 * 1; s.width += 2 * 3; return s; } - (void) setAction: (SEL)aSelector { [super setAction: aSelector]; [_menu update]; } - (void) setTarget: (id)anObject { [super setTarget: anObject]; [_menu update]; } // // NSCoding protocol // - (void) encodeWithCoder: (NSCoder*)aCoder { [super encodeWithCoder: aCoder]; if ([aCoder allowsKeyedCoding]) { [aCoder encodeBool: [self altersStateOfSelectedItem] forKey: @"NSAltersState"]; [aCoder encodeBool: [self usesItemFromMenu] forKey: @"NSUsesItemFromMenu"]; [aCoder encodeInt: [self arrowPosition] forKey: @"NSArrowPosition"]; [aCoder encodeInt: [self preferredEdge] forKey: @"NSPreferredEdge"]; [aCoder encodeInt: [self indexOfSelectedItem] forKey: @"NSSelectedIndex"]; [aCoder encodeBool: [self pullsDown] forKey: @"NSPullDown"]; // encode the menu, if present. if (_menu != nil) { [aCoder encodeObject: _menu forKey: @"NSMenu"]; } } else { int flag; [aCoder encodeObject: _menu]; [aCoder encodeConditionalObject: [self selectedItem]]; flag = _pbcFlags.pullsDown; [aCoder encodeValueOfObjCType: @encode(int) at: &flag]; flag = _pbcFlags.preferredEdge; [aCoder encodeValueOfObjCType: @encode(int) at: &flag]; flag = _pbcFlags.usesItemFromMenu; [aCoder encodeValueOfObjCType: @encode(int) at: &flag]; flag = _pbcFlags.altersStateOfSelectedItem; [aCoder encodeValueOfObjCType: @encode(int) at: &flag]; flag = _pbcFlags.arrowPosition; [aCoder encodeValueOfObjCType: @encode(int) at: &flag]; } } - (id) initWithCoder: (NSCoder*)aDecoder { NSMenu *menu; self = [super initWithCoder: aDecoder]; if (!self) return nil; if ([aDecoder allowsKeyedCoding]) { if ([aDecoder containsValueForKey: @"NSAltersState"]) { BOOL alters = [aDecoder decodeBoolForKey: @"NSAltersState"]; [self setAltersStateOfSelectedItem: alters]; } if ([aDecoder containsValueForKey: @"NSUsesItemFromMenu"]) { BOOL usesItem = [aDecoder decodeBoolForKey: @"NSUsesItemFromMenu"]; [self setUsesItemFromMenu: usesItem]; } if ([aDecoder containsValueForKey: @"NSArrowPosition"]) { NSPopUpArrowPosition position = [aDecoder decodeIntForKey: @"NSArrowPosition"]; [self setArrowPosition: position]; } if ([aDecoder containsValueForKey: @"NSPreferredEdge"]) { NSRectEdge edge = [aDecoder decodeIntForKey: @"NSPreferredEdge"]; [self setPreferredEdge: edge]; } if ([aDecoder containsValueForKey: @"NSSelectedIndex"]) { int selectedIdx = [aDecoder decodeIntForKey: @"NSSelectedIndex"]; [self selectItem: [self itemAtIndex: selectedIdx]]; } if ([aDecoder containsValueForKey: @"NSPullDown"]) { BOOL pullDown = [aDecoder decodeBoolForKey: @"NSPullDown"]; [self setPullsDown: pullDown]; } menu = [aDecoder decodeObjectForKey: @"NSMenu"]; [self setMenu: nil]; [self setMenu: menu]; [self setAutoenablesItems: NO]; [self setEnabled: YES]; } else { int flag; id selectedItem; int version = [aDecoder versionForClassName: @"NSPopUpButtonCell"]; menu = [aDecoder decodeObject]; /* FIXME: This same ivar already gets set in NSCell initWithCoder, but there it is used directly not via a method call. So here we first unset it and than set it again as our setMenu: method tries to optimize duplicate calls. */ [self setMenu: nil]; [self setMenu: menu]; selectedItem = [aDecoder decodeObject]; [aDecoder decodeValueOfObjCType: @encode(int) at: &flag]; _pbcFlags.pullsDown = flag; [aDecoder decodeValueOfObjCType: @encode(int) at: &flag]; _pbcFlags.preferredEdge = flag; [aDecoder decodeValueOfObjCType: @encode(int) at: &flag]; _pbcFlags.usesItemFromMenu = flag; [aDecoder decodeValueOfObjCType: @encode(int) at: &flag]; _pbcFlags.altersStateOfSelectedItem = flag; [aDecoder decodeValueOfObjCType: @encode(int) at: &flag]; _pbcFlags.arrowPosition = flag; if (version < 2) { int i; // Not the stored format did change but the interpretation of it. // in version 1 most of the ivars were not used, so their values may // be arbitray. We overwrite them with valid settings. [self setPullsDown: _pbcFlags.pullsDown]; _pbcFlags.usesItemFromMenu = YES; for (i = 0; i < [_menu numberOfItems]; i++) { id anItem = [menu itemAtIndex: i]; [anItem setOnStateImage: nil]; [anItem setMixedStateImage: nil]; } [self setEnabled: YES]; } if (version < 3) { [self setPreferredEdge: NSMinYEdge]; [self setArrowPosition: NSPopUpArrowAtCenter]; } [self selectItem: selectedItem]; } return self; } @end @implementation NSPopUpButtonCell (CocoaExtensions) /* * The selector for this method gets used by menu items in Apple NIB files. */ - (void) _popUpItemAction: (id)sender { [NSApp sendAction: [self action] to: [self target] from: self]; } @end