/**
An NSComboBoxCell is what we can call a completion/choices box cell, derived from
NSTextFieldCell, it allows you to enter text like in a text field but also to click
in the ellipsis button (indicating the fact other user inputs are possible) on
the right of it to obtain a list of choices, you can use them as the text field
value by selecting a row in this list. You can also obtain direct completion
when it is enabled via setCompletes:
to get a suggested text
field value updated as you type.
Like other NSCell classes, NSComboBoxCell has a matching NSControl named NSComboBox which is relying on it to implement the combo box behavior in a standalone control.
No special instructions to use NSComboBoxCell or text to detail the implementation.
*/ @implementation NSComboBoxCell /* * Class methods */ + (void) initialize { if (self == [NSComboBoxCell class]) { [NSComboBoxCell setVersion: 2]; nc = [NSNotificationCenter defaultCenter]; } } - (id) initTextCell: (NSString *)aString { self = [super initTextCell: aString]; // Implicitly set by allocation: // //_dataSource = nil; //_buttonCell = nil; //_usesDataSource = NO; //_completes = NO; _popUpList = [[NSMutableArray alloc] init]; _hasVerticalScroller = YES; _visibleItems = 10; _intercellSpacing = NSMakeSize(3.0, 2.0); _itemHeight = 16; _selectedItem = -1; [self _loadButtonCell]; return self; } - (void) dealloc { RELEASE(_buttonCell); RELEASE(_popUpList); [super dealloc]; } - (id) copyWithZone: (NSZone*)zone { NSComboBoxCell *c = [super copyWithZone: zone]; c->_buttonCell = [_buttonCell copyWithZone: zone]; [c->_buttonCell setTarget: c]; c->_popUpList = [_popUpList copyWithZone: zone]; return c; } /** * Returns YES when the combo box cell displays a vertical scroller for its * list, returns NO otherwise. * Take note that the scroller will be displayed even when the sum of the items * height in the list is inferior to the minimal height of the list displayed * area. */ - (BOOL) hasVerticalScroller { return _hasVerticalScroller; } /** * Sets whether the combo box cell list displays a vertical scroller, by default * it is the case. When flag is NO and the combo cell list has more * items (either in its default list or from its data source) than the number * returned bynumberOfVisibleItems
, only a subset of them will be
* displayed. Uses scroll related methods to position this subset in the combo
* box cell list.
* Take note that the scroller will be displayed even when the sum of the items
* height in the list is inferior to the minimal height of the list displayed
* area.
*/
- (void) setHasVerticalScroller: (BOOL)flag
{
_hasVerticalScroller = flag;
}
/**
* Returns the width and the height (as the values of an NSSize variable)
* between each item of the combo box cell list.
*/
- (NSSize) intercellSpacing
{
return _intercellSpacing;
}
/**
* Sets the width and the height between each item of the combo box cell list to
* the values in aSize.
*/
- (void) setIntercellSpacing: (NSSize)aSize
{
_intercellSpacing = aSize;
}
/**
* Returns the height of the items in the combo box cell list.
*/
- (CGFloat) itemHeight
{
return _itemHeight;
}
/**
* Sets the height of the items in the combo box cell list to
* itemHeight.
*/
- (void) setItemHeight: (CGFloat)itemHeight
{
if (itemHeight > 14.0)
_itemHeight = itemHeight;
}
/**
* Returns the maximum number of allowed items to be displayed in the combo box
* cell list.
*/
- (NSInteger) numberOfVisibleItems
{
return _visibleItems;
}
/**
* Sets the maximum number of allowed items to be displayed in the combo box
* cell list.
*/
- (void) setNumberOfVisibleItems: (NSInteger)visibleItems
{
if (visibleItems > 10)
_visibleItems = visibleItems;
}
/**
* Marks the combo box cell in order to have its items list reloaded in the
* case it uses a data source, and to have it redisplayed.
*/
- (void) reloadData
{
[_popup reloadData];
}
/**
* Informs the combo box cell that the number of items in its data source has
* changed, in order to permit to the scrollers in its displayed list being
* updated without needing the reload of the data.
* It is recommended to use this method with a data source that continually
* receives data in the background, to keep the the combo box cell responsive to
* the user while the data is received.
* Take a look at the NSComboBoxDataSource
informal protocol
* specification to know more on the messages NSComboBox sends to its data
* source.
*/
- (void) noteNumberOfItemsChanged
{
[_popup noteNumberOfItemsChanged];
}
/**
* Returns YES when the combo box cell uses a data source (which is external) to
* populate its items list, otherwise returns NO in the case it uses its default
* list.
*/
- (BOOL) usesDataSource
{
return _usesDataSource;
}
/**
* Sets according to flag whether the combo box cell uses a data
* source (which is external) to populate its items list.
*/
- (void) setUsesDataSource: (BOOL)flag
{
_usesDataSource = flag;
}
/**
* Scrolls the combo box cell list vertically in order to have the item at
* index in the closest position relative to the top. There is no
* need to have the list displayed when this method is invoked.
*/
- (void) scrollItemAtIndexToTop: (NSInteger)index
{
[_popup scrollItemAtIndexToTop: index];
}
/**
* Scrolls the combo box cell list vertically in order to have the item at
* index visible. There is no need to have the list displayed when
* this method is invoked.
*/
- (void) scrollItemAtIndexToVisible: (NSInteger)index
{
[_popup scrollItemAtIndexToVisible: index];
}
/**
* Selects the combo box cell list row at index.
* Take note no changes occurs in the combo box cell list when this method is
* called.
* Posts an NSComboBoxSelectionDidChangeNotification to the default notification
* center when there is a new selection different from the previous one.
*/
- (void) selectItemAtIndex: (NSInteger)index
{
// Method called by GSComboWindow when a selection is done in the table view or
// the browser
if (index < 0 || [self numberOfItems] <= index)
return; // FIXME: Probably we should raise an exception
if (_selectedItem != index)
{
[self _setSelectedItem: index];
[_popup selectItemAtIndex: index];
// This method call will not create a infinite loop when the index has been
// already set by a mouse click because the method is not completed when the
// current index is not different from the index parameter
[nc postNotificationName: NSComboBoxSelectionDidChangeNotification
object: [self controlView]
userInfo: nil];
}
}
/**
* Deselects the combo box cell list row at index in the case this
* row is selected.
* Posts an NSComboBoxSelectionDidChangeNotification to the default notification
* center, when there is a new selection.
*/
- (void) deselectItemAtIndex: (NSInteger)index
{
if (_selectedItem == index)
{
[self _setSelectedItem: -1];
[_popup deselectItemAtIndex: index];
[nc postNotificationName: NSComboBoxSelectionDidChangeNotification
object: [self controlView]
userInfo: nil];
}
}
/**
* Returns the index of the selected item in the combo box cell list or -1 when
* there is no selection, the selected item can be related to the data source
* object in the case usesDataSource
returns YES else to the
* default items list.
*/
- (NSInteger) indexOfSelectedItem
{
return _selectedItem;
}
/**
* Returns the number of items in the the combo box cell list, the numbers of
* items can be be related to the data source object in the case
* usesDataSource
returns YES else to the default items list.
*/
- (NSInteger) numberOfItems
{
if (_usesDataSource)
{
if (_dataSource == nil)
{
NSLog(@"%@: No data source currently specified", self);
}
else if ([_dataSource respondsToSelector:
@selector(numberOfItemsInComboBox:)])
{
return [_dataSource numberOfItemsInComboBox:
(NSComboBox *)[self controlView]];
}
else if ([_dataSource respondsToSelector:
@selector(numberOfItemsInComboBoxCell:)])
{
return [_dataSource numberOfItemsInComboBoxCell: self];
}
}
else
{
return [_popUpList count];
}
return 0;
}
/**
* Returns the combo box cell data source object which is reponsible to provide
* the data to be displayed. To know how to implement a data source object,
* take a look at the NSComboBoxDataSource informal protocol description. In
* the case usesDataSource
returns NO, this method logs a warning.
*/
- (id) dataSource
{
return _dataSource;
}
/**
* Sets the combo box cell data source to aSource. Just calling this
* method doesn't set usesDataSource
to return YES, you must call
* setUsesDataSource:
with YES before or a warning will be logged.
* To know how to implement a data source objects, take a look at the
* NSComboBoxDataSource informal protocol description. When aSource
* doesn't respond to the methods numberOfItemsInComboBox:
* comboBox:objectValueForItemAtIndex:
, this method
* logs a warning.
*/
- (void) setDataSource: (id)aSource
{
if (_usesDataSource == NO)
{
NSLog(@"%@: This method is invalid, this combo box is not set to use a data source",
self);
}
else
{
_dataSource = aSource;
}
}
/**
* Adds an item to the combo box cell default items list which is used when
* usesDataSource
returns NO. In the case
* usesDataSource
returns YES, this method logs a warning.
*/
- (void) addItemWithObjectValue: (id)object
{
if (_usesDataSource)
{
NSLog(@"%@: This method is invalid, this combo box is set to use a data source",
self);
}
else
{
[_popUpList addObject: object];
}
[self reloadData];
}
/**
* Adds several items in an array to the combo box cell default items list which
* is used when usesDataSource
returns NO. In the case
* usesDataSource
returns YES, this method logs a warning.
*/
- (void) addItemsWithObjectValues: (NSArray *)objects
{
if (_usesDataSource)
{
NSLog(@"%@: This method is invalid, this combo box is set to use a data source",
self);
}
else
{
[_popUpList addObjectsFromArray: objects];
}
[self reloadData];
}
/**
* Inserts an item in the combo box cell default items list which
* is used when usesDataSource
returns NO. In the case
* usesDataSource
returns YES, this method logs a warning.
*/
- (void) insertItemWithObjectValue: (id)object atIndex: (NSInteger)index
{
if (_usesDataSource)
{
NSLog(@"%@: This method is invalid, this combo box is set to use a data source",
self);
}
else
{
[_popUpList insertObject: object atIndex: index];
}
[self reloadData];
}
/**
* Removes an item in the combo box cell default items list which
* is used when usesDataSource
returns NO. In the case
* usesDataSource
returns YES, this method logs a warning.
*/
- (void) removeItemWithObjectValue: (id)object
{
if (_usesDataSource)
{
NSLog(@"%@: This method is invalid, this combo box is set to use a data source",
self);
}
else
{
[_popUpList removeObject: object];
}
[self reloadData];
}
/**
* Removes the item with the specified index in the combo box cell
* default items list which is used when usesDataSource
returns NO.
* In the case usesDataSource
returns YES, this method logs a warning.
*/
- (void) removeItemAtIndex: (NSInteger)index
{
if (_usesDataSource)
{
NSLog(@"%@: This method is invalid, this combo box is set to use a data source",
self);
}
else
{
[_popUpList removeObjectAtIndex: index];
}
[self reloadData];
}
/**
* Removes all the items in the combo box cell default items list which is used
* when usesDataSource
returns NO. In the case
* usesDataSource
returns YES, this method logs a warning.
*/
- (void) removeAllItems
{
if (_usesDataSource)
{
NSLog(@"%@: This method is invalid, this combo box is set to use a data source",
self);
}
else
{
[_popUpList removeAllObjects];
}
[self reloadData];
}
/**
* Selects the first item in the default combo box cell list which is equal to
* object. In the case usesDataSource
returns YES, this
* method logs a warning.
* Take note that this method doesn't update the text field part value.
* Posts an NSComboBoxSelectionDidChange notification to the default
* notification center when the new selection is different than the previous
* one.
*/
- (void) selectItemWithObjectValue: (id)object
{
if (_usesDataSource)
{
NSLog(@"%@: This method is invalid, this combo box is set to use a data source",
self);
}
else
{
NSInteger i = [_popUpList indexOfObject: object];
if (i == NSNotFound)
i = -1;
[self selectItemAtIndex: i];
}
}
/**
* Returns the object value at index within combo box cell default
* items list. When the index is beyond the end of the list, an NSRangeException is
* raised. In the case usesDataSource
returns YES, this method logs
* a warning.
*/
- (id) itemObjectValueAtIndex: (NSInteger)index
{
if (_usesDataSource)
{
NSLog(@"%@: This method is invalid, this combo box is set to use a data source",
self);
return nil;
}
else
{
return [_popUpList objectAtIndex: index];
}
}
/* FIXME: Not sure, if this is the best way to implement objectValue,
* perhaps it would be better to store the current value with setObjectValue:
* whenever it changes.
*/
- (id) objectValue
{
NSInteger index = [self indexOfSelectedItem];
if (index == -1)
{
return nil;
}
else
{
if (_usesDataSource)
{
if (_dataSource == nil)
{
NSLog(@"%@: No data source currently specified", self);
return nil;
}
if ([_dataSource respondsToSelector:
@selector(comboBox:objectValueForItemAtIndex:)])
{
return [_dataSource comboBox: (NSComboBox *)[self controlView]
objectValueForItemAtIndex: index];
}
else if ([_dataSource respondsToSelector:
@selector(comboBoxCell:objectValueForItemAtIndex:)])
{
return [_dataSource comboBoxCell: self
objectValueForItemAtIndex: index];
}
}
else
{
return [self itemObjectValueAtIndex: index];
}
}
return nil;
}
/**
* Returns the object value of the selected item in the combo box cell default
* items list or nil when there is no selection. In the case
* usesDataSource
returns YES, this method logs a warning.
*/
- (id) objectValueOfSelectedItem
{
if (_usesDataSource)
{
NSLog(@"%@: This method is invalid, this combo box is set to use a data source",
self);
return nil;
}
else
{
NSInteger index = [self indexOfSelectedItem];
if (index == -1)
{
return nil;
}
else
{
return [_popUpList objectAtIndex: index];
}
}
}
/**
* Returns the lowest index associated with a value in the combo box
* cell default items list, which is equal to object, and returns
* NSNotFound when there is no such value. In the case
* usesDataSource
returns YES, this method logs a warning.
*/
- (NSInteger) indexOfItemWithObjectValue: (id)object
{
if (_usesDataSource)
{
NSLog(@"%@: This method is invalid, this combo box is set to use a data source",
self);
return 0;
}
return [_popUpList indexOfObject: object];
}
/**
* Returns the combo box cell default items list in an array.
*/
- (NSArray *) objectValues
{
if (_usesDataSource)
{
NSLog(@"%@: This method is invalid, this combo box is set to use a data source",
self);
return nil;
}
return _popUpList;
}
// Text completion
/**
* Returns a string by looking in the combo box cell list for an item wich
* starts with substring, or nil when there is no such string.
* substring is equal to what the user entered in the text field
* part.
* You rarely needs to call this method explicitly in your code.
* By default, the implementation of this method first checks whether the combo
* box cell uses a data source and whether the data source responds to
* comboBox:completedString:
or comboBoxCell:completedString:
.
* When it is the case, it uses this method to return str, else this
* method goes through the combo box cell items one by one and returns the first
* item found starting with substring.
* In the case, you want another behavior, you can override this method without
* need to call the superclass method.
*/
- (NSString *) completedString: (NSString *)substring
{
if (nil == substring)
{
return nil;
}
if (_usesDataSource)
{
if (_dataSource == nil)
{
NSLog(@"%@: No data source currently specified", self);
}
else if ([_dataSource respondsToSelector: @selector(comboBox:completedString:)])
{
return [_dataSource comboBox: (NSComboBox *)[self controlView]
completedString: substring];
}
else if ([_dataSource respondsToSelector: @selector(comboBoxCell:completedString:)])
{
return [_dataSource comboBoxCell: self completedString: substring];
}
else
{
NSInteger i;
for (i = 0; i < [self numberOfItems]; i++)
{
NSString *str = [self _stringValueAtIndex: i];
if ([str length] > [substring length] && [str hasPrefix: substring])
return str;
}
}
}
else
{
NSUInteger i;
for (i = 0; i < [_popUpList count]; i++)
{
NSString *str = [[_popUpList objectAtIndex: i] description];
if ([str length] > [substring length] && [str hasPrefix: substring])
return str;
}
}
return substring;
}
/**
* Returns YES when the combo box cell automatic completion is active, returns
* NO otherwise.
* Take a look at the setCompletes:
method documentation to know
* how the automatic completion works.
*/
- (BOOL) completes
{
return _completes;
}
/**
* Sets whether the combo box cell automatic completion is active or not.
* The automatic completion tries to complete what the user types in the text
* field part, it tries to complete only when the the user adds characters at
* the end of the string, not when it deletes characters or when the insertion
* point precedes the end of the string.
* To do the automatic completion, the completedString:
method is
* called, and when the returned string is longer than the current one in the text
* field, the completion occurs and the completed part gets selected.
*/
- (void) setCompletes: (BOOL)completes
{
_completes = completes;
}
- (BOOL) isButtonBordered
{
return [_buttonCell isBordered];
}
- (void) setButtonBordered:(BOOL)flag
{
[_buttonCell setBordered: flag];
}
#define ComboBoxHeight 21 // FIX ME: All this stuff shouldn't be hardcoded
#define ButtonWidth 17
#define ButtonHeight 17
#define BorderSize 2
// The inset border for the top and the bottom of the button
/*
* Inlined methods
*/
static inline NSRect textCellFrameFromRect(NSRect cellRect)
// Not the drawed part, precises just the part which receives events
{
return NSMakeRect(NSMinX(cellRect),
NSMinY(cellRect),
NSWidth(cellRect) - ButtonWidth - BorderSize,
NSHeight(cellRect));
}
static inline NSRect buttonCellFrameFromRect(NSRect cellRect)
{
return NSMakeRect(NSMaxX(cellRect) - ButtonWidth - BorderSize,
NSMinY(cellRect) + BorderSize,
ButtonWidth,
ButtonHeight);
}
// Overridden
+ (BOOL) prefersTrackingUntilMouseUp
{
return YES;
/* Needed to have the clickability of the button take in account when the tracking happens.
This method is call by the NSControl -mouseDown: method with the code :
[_cell trackMouse: e
inRect: _bounds
ofView: self
untilMouseUp: [[_cell class] prefersTrackingUntilMouseUp]] */
}
- (NSSize) cellSize
{
NSSize textSize;
NSSize buttonSize;
NSSize mySize;
/* Simple version takes the size from text field. A more useful one could
loop over the strings of the combo box and calculate the maximal width of
all strings. */
textSize = [super cellSize];
// Or should we use the hard coded values from above here?
buttonSize = [_buttonCell cellSize];
mySize.height = MAX(textSize.height, buttonSize.height);
mySize.width = textSize.width + BorderSize + buttonSize.width;
return mySize;
}
- (void) drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
NSRect rect = cellFrame;
// FIX ME: Is this test case below with the method call really needed ?
if ([GSCurrentContext() isDrawingToScreen])
{
[super drawInteriorWithFrame: textCellFrameFromRect(rect)
inView: controlView];
[_buttonCell drawWithFrame: buttonCellFrameFromRect(rect)
inView: controlView];
}
else
{
[super drawInteriorWithFrame: rect inView: controlView];
}
// Used by GSComboWindow to appear in the right position
_lastValidFrame = cellFrame;
}
- (void) highlight: (BOOL)flag
withFrame: (NSRect)cellFrame
inView: (NSView *)controlView
{
NSRect rect = cellFrame;
// FIX ME: Is this test case below with the method call really needed ?
if ([GSCurrentContext() isDrawingToScreen])
{
[super highlight: flag
withFrame: textCellFrameFromRect(rect)
inView: controlView];
[_buttonCell highlight: flag
withFrame: buttonCellFrameFromRect(rect)
inView: controlView];
}
else
{
[super highlight: flag withFrame: rect inView: controlView];
}
}
/** Overrides NSCell trackMouse:inRect:ofView:untilMouseUp:
method to establish a
* new method behavior.
* In the case flag is NO, returns NO when the mouse down occurs in the text
* cell part or when the mouse down occurs in the button cell part followed by a
* mouse up outside, otherwise returns YES (when both the mouse down and the
* mouse up occurs in the button cell part).
* In the case flag is YES, returns NO when the mouse occurs in the text
* cell part, otherwise returns YES (when the mouse down occurs in the button cell
* part).
*/
- (BOOL) trackMouse: (NSEvent *)theEvent
inRect: (NSRect)cellFrame
ofView: (NSView *)controlView
untilMouseUp: (BOOL)flag
{
NSPoint point;
BOOL isFlipped = [controlView isFlipped];
NSRect buttonRect = buttonCellFrameFromRect(cellFrame);
NSRect textRect = textCellFrameFromRect(cellFrame);
BOOL result = NO;
// FIXME: May be that should be set by NSActionCell
if (_control_view != controlView)
_control_view = controlView;
// Used by GSComboWindow to appear in the right position
_lastValidFrame = cellFrame;
point = [controlView convertPoint: [theEvent locationInWindow]
fromView: nil];
if (NSMouseInRect(point, textRect, isFlipped))
{
return NO;
}
else if (NSMouseInRect(point, buttonRect, isFlipped))
{
NSEvent *e = theEvent;
BOOL isMouseUp = NO;
NSUInteger eventMask = NSLeftMouseDownMask | NSLeftMouseUpMask
| NSMouseMovedMask | NSLeftMouseDraggedMask | NSOtherMouseDraggedMask
| NSRightMouseDraggedMask;
NSPoint location;
while (isMouseUp == NO) // Loop until mouse goes up
{
location = [controlView convertPoint: [e locationInWindow] fromView: nil];
// Ask the cell to track the mouse only when the mouse is within the cell
if (NSMouseInRect(location, buttonRect, isFlipped))
{
[_buttonCell setHighlighted: YES];
[controlView setNeedsDisplayInRect: cellFrame];
result = [_buttonCell trackMouse: e
inRect: buttonRect
ofView: controlView
untilMouseUp: [NSButtonCell prefersTrackingUntilMouseUp]];
isMouseUp = result;
[_buttonCell setHighlighted: NO];
[controlView setNeedsDisplayInRect: cellFrame];
}
if (isMouseUp == NO)
{
e = [NSApp nextEventMatchingMask: eventMask
untilDate: [NSDate distantFuture]
inMode: NSEventTrackingRunLoopMode
dequeue: YES];
if ([e type] == NSLeftMouseUp)
isMouseUp = YES;
}
}
if (flag)
{
return YES;
}
else
{
return NO;
}
}
return NO; // Pathological case, normally never happens
}
- (void) resetCursorRect: (NSRect)cellFrame inView: (NSView *)controlView
{
[super resetCursorRect: textCellFrameFromRect(cellFrame)
inView: controlView];
}
- (void) setEnabled: (BOOL)flag
{
[_buttonCell setEnabled: flag];
[super setEnabled: flag];
}
// NSCoding
/**
* Encodes the combo box cell using encoder. take note that when it
* uses a data source, the data source is conditionally encoded.
*/
- (void) encodeWithCoder: (NSCoder *)coder
{
[super encodeWithCoder: coder];
if ([coder allowsKeyedCoding])
{
[coder encodeBool: [self hasVerticalScroller] forKey: @"NSHasVerticalScroller"];
[coder encodeInteger: [self numberOfVisibleItems] forKey: @"NSVisibleItemCount"];
[coder encodeBool: [self completes] forKey: @"NSCompletes"];
[coder encodeDouble: _intercellSpacing.width forKey: @"NSIntercellSpacingWidth"];
[coder encodeDouble: _intercellSpacing.height forKey: @"NSIntercellSpacingHeight"];
[coder encodeDouble: [self itemHeight] forKey: @"NSRowHeight"];
[coder encodeBool: [self usesDataSource] forKey: @"NSUsesDataSource"];
[coder encodeObject: [self dataSource] forKey: @"NSDataSource"];
[coder encodeObject: _popUpList forKey: @"NSPopUpListData"];
}
else
{
[coder encodeValueOfObjCType: @encode(id) at: &_popUpList];
[coder encodeValueOfObjCType: @encode(BOOL) at: &_usesDataSource];
[coder encodeValueOfObjCType: @encode(BOOL) at: &_hasVerticalScroller];
[coder encodeValueOfObjCType: @encode(BOOL) at: &_completes];
[coder encodeValueOfObjCType: @encode(BOOL) at: &_usesDataSource];
encode_NSInteger(coder, &_visibleItems);
[coder encodeValueOfObjCType: @encode(NSSize) at: &_intercellSpacing];
[coder encodeValueOfObjCType: @encode(float) at: &_itemHeight];
encode_NSInteger(coder, &_selectedItem);
if (_usesDataSource == YES)
[coder encodeConditionalObject: _dataSource];
}
}
/**
* Initializes the combo box cell with data linked to decoder. Take
* note that when the decoded instance uses a data source,
* initWithCoder: decodes the data source.
* Finally, returns thr initialized object.
*/
- (id) initWithCoder: (NSCoder *)aDecoder
{
self = [super initWithCoder: aDecoder];
if (nil == self)
return nil;
if ([aDecoder allowsKeyedCoding])
{
//id delegate = [aDecoder decodeObjectForKey: @"NSDelegate"];
//id table = [aDecoder decodeObjectForKey: @"NSTableView"];
if ([aDecoder containsValueForKey: @"NSHasVerticalScroller"])
{
[self setHasVerticalScroller: [aDecoder decodeBoolForKey:
@"NSHasVerticalScroller"]];
}
if ([aDecoder containsValueForKey: @"NSVisibleItemCount"])
{
[self setNumberOfVisibleItems: [aDecoder decodeIntegerForKey:
@"NSVisibleItemCount"]];
}
if ([aDecoder containsValueForKey: @"NSCompletes"])
{
[self setCompletes: [aDecoder decodeBoolForKey: @"NSCompletes"]];
}
if ([aDecoder containsValueForKey: @"NSIntercellSpacingWidth"])
{
_intercellSpacing.width = [aDecoder decodeDoubleForKey:
@"NSIntercellSpacingWidth"];
}
if ([aDecoder containsValueForKey: @"NSIntercellSpacingHeight"])
{
_intercellSpacing.height = [aDecoder decodeDoubleForKey:
@"NSIntercellSpacingHeight"];
}
if ([aDecoder containsValueForKey: @"NSRowHeight"])
{
[self setItemHeight: [aDecoder decodeDoubleForKey:
@"NSRowHeight"]];
}
if ([aDecoder containsValueForKey: @"NSUsesDataSource"])
{
[self setUsesDataSource: [aDecoder decodeBoolForKey:
@"NSUsesDataSource"]];
}
if ([aDecoder containsValueForKey: @"NSDataSource"])
{
[self setDataSource: [aDecoder decodeObjectForKey: @"NSDataSource"]];
}
if ([aDecoder containsValueForKey: @"NSPopUpListData"])
{
ASSIGN(_popUpList, [aDecoder decodeObjectForKey: @"NSPopUpListData"]);
}
}
else
{
BOOL dummy;
if ([aDecoder versionForClassName: @"NSComboBoxCell"] < 2)
{
// In previous version we decode _buttonCell, we just discard the decoded value here
id previouslyEncodedButton;
[aDecoder decodeValueOfObjCType: @encode(id) at: &previouslyEncodedButton];
}
[aDecoder decodeValueOfObjCType: @encode(id) at: &_popUpList];
RETAIN(_popUpList);
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_usesDataSource];
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_hasVerticalScroller];
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_completes];
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &dummy];
decode_NSInteger(aDecoder, &_visibleItems);
[aDecoder decodeValueOfObjCType: @encode(NSSize) at: &_intercellSpacing];
[aDecoder decodeValueOfObjCType: @encode(float) at: &_itemHeight];
decode_NSInteger(aDecoder, &_selectedItem);
if (_usesDataSource == YES)
[self setDataSource: [aDecoder decodeObject]];
}
[self _loadButtonCell];
return self;
}
- (void) selectWithFrame: (NSRect)aRect
inView: (NSView *)controlView
editor: (NSText *)textObj
delegate: (id)anObject
start: (NSInteger)selStart
length: (NSInteger)selLength
{
[super selectWithFrame: textCellFrameFromRect(aRect)
inView: controlView
editor: textObj
delegate: anObject
start: selStart
length: selLength];
[nc addObserver: self
selector: @selector(textDidChange:)
name: NSTextDidChangeNotification
object: textObj];
[nc addObserver: self
selector: @selector(textViewDidChangeSelection:)
name: NSTextViewDidChangeSelectionNotification
object: textObj];
// This method is called when the cell obtains the focus;
// don't know why the next method editWithFrame: is not called
}
- (void) editWithFrame: (NSRect)frame
inView: (NSView *)controlView
editor: (NSText *)textObj
delegate: (id)delegate
event: (NSEvent *)theEvent
{
[super editWithFrame: textCellFrameFromRect(frame)
inView: controlView
editor: textObj
delegate: delegate
event: theEvent];
/*
[nc addObserver: self
selector: @selector(textDidChange:)
name: NSTextDidChangeNotification
object: textObj];
[nc addObserver: self
selector: @selector(textViewDidChangeSelection:)
name: NSTextViewDidChangeSelectionNotification
object: textObj]; */
}
- (void) endEditing: (NSText *)editor
{
/* Close the pop up if it is still open. This may happen, e.g., when the
user presses the Tab key to shift focus to a different cell or view. */
if (_popup)
[_popup onWindowEdited: nil];
[super endEditing: editor];
[nc removeObserver: self name: NSTextDidChangeNotification object: editor];
[nc removeObserver: self
name: NSTextViewDidChangeSelectionNotification
object: editor];
}
- (void) textViewDidChangeSelection: (NSNotification *)notification
{
_prevSelectedRange = [[[notification userInfo]
objectForKey: @"NSOldSelectedCharacterRange"] rangeValue];
}
- (void) textDidChange: (NSNotification *)notification
{
NSText *textObject = [notification object];
if ([self completes])
{
NSString *myString = [[textObject string] copy];
NSString *more;
NSUInteger myStringLength = [myString length];
NSUInteger location, length;
NSRange selectedRange = [textObject selectedRange];
if (myStringLength != 0
&& selectedRange.location == myStringLength
&& _prevSelectedRange.location < selectedRange.location)
{
more = [self completedString: myString];
if ((more != nil) && [more isEqualToString: myString] == NO)
{
[textObject setString: more];
location = myStringLength;
length = [more length] - location;
[textObject setSelectedRange: NSMakeRange(location, length)];
[textObject scrollRangeToVisible: NSMakeRange(location, length)];
}
}
RELEASE(myString);
}
}
@end
@implementation NSComboBoxCell (GNUstepPrivate)
- (NSString *) _stringValueAtIndex: (NSInteger)index
{
if (_usesDataSource == NO)
{
return [[self itemObjectValueAtIndex: index] description];
}
else
{
if (_dataSource == nil)
{
NSLog(@"%@: No data source currently specified", self);
return nil;
}
else if ([_dataSource respondsToSelector:
@selector(comboBox:objectValueForItemAtIndex:)])
{
return [[_dataSource comboBox: (NSComboBox *)[self controlView]
objectValueForItemAtIndex: index] description];
}
else if ([_dataSource respondsToSelector:
@selector(comboBoxCell:objectValueForItemAtIndex:)])
{
return [[_dataSource comboBoxCell: self
objectValueForItemAtIndex: index] description];
}
}
return nil;
}
- (void) _performClickWithFrame: (NSRect)cellFrame
inView: (NSView *)controlView
{
NSWindow *cvWindow = [controlView window];
NSRect buttonRect = buttonCellFrameFromRect(cellFrame);
_control_view = controlView;
[controlView lockFocus];
[_buttonCell highlight: YES
withFrame: buttonRect
inView: controlView];
[controlView unlockFocus];
[cvWindow flushWindow];
[self _didClickWithinButton: self];
[controlView lockFocus];
[_buttonCell highlight: NO
withFrame: buttonRect
inView: controlView];
[controlView unlockFocus];
[cvWindow flushWindow];
}
- (void) _didClickWithinButton: (id)sender
{
NSView *controlView = [self controlView];
if ((_cell.is_disabled) || (controlView == nil))
return;
[nc postNotificationName: NSComboBoxWillPopUpNotification
object: controlView
userInfo: nil];
_popup = [self _popUp];
[_popup popUpForComboBoxCell: self];
_popup = nil;
[nc postNotificationName: NSComboBoxWillDismissNotification
object: controlView
userInfo: nil];
}
- (BOOL) _isWantedEvent: (NSEvent *)event
{
NSPoint loc;
NSWindow *window = [event window];
NSView *controlView = [self controlView];
if (window == [[self controlView] window])
{
loc = [event locationInWindow];
loc = [controlView convertPoint: loc fromView: nil];
return NSMouseInRect(loc, [self _textCellFrame], [controlView isFlipped]);
}
else
{
return NO;
}
}
- (GSComboWindow *) _popUp
{
return [GSComboWindow defaultPopUp];
}
- (NSRect) _textCellFrame
{
return textCellFrameFromRect(_lastValidFrame);
}
- (void) _setSelectedItem: (NSInteger)index
{
_selectedItem = index;
}
- (void) _loadButtonCell
{
_buttonCell = [[NSButtonCell alloc] initImageCell:
[NSImage imageNamed: @"NSComboArrow"]];
[_buttonCell setImagePosition: NSImageOnly];
[_buttonCell setButtonType: NSMomentaryPushButton];
[_buttonCell setHighlightsBy: NSPushInCellMask];
[_buttonCell setBordered: YES];
[_buttonCell setTarget: self];
[_buttonCell setAction: @selector(_didClickWithinButton:)];
[_buttonCell setEnabled: [self isEnabled]];
}
- (void) _selectCompleted
{
NSString *more;
NSUInteger index = NSNotFound;
more = [self completedString: [self stringValue]];
if (_usesDataSource)
{
if (_dataSource == nil)
{
NSLog(@"%@: No data source currently specified", self);
}
else
{
if ([_dataSource respondsToSelector:
@selector(comboBoxCell:indexOfItemWithStringValue:)])
{
index = [_dataSource comboBoxCell: self
indexOfItemWithStringValue: more];
}
}
}
else
{
index = [[self objectValues] indexOfObject: more];
}
if (index != NSNotFound)
{
[self _setSelectedItem: index];
}
// Otherwise keep old selection
}
@end