mirror of
https://github.com/gnustep/libs-gui.git
synced 2025-04-23 05:10:47 +00:00
1941 lines
54 KiB
Objective-C
1941 lines
54 KiB
Objective-C
/** <title>NSComboBoxCell</title>
|
|
|
|
Copyright (C) 1999 Free Software Foundation, Inc.
|
|
|
|
Author: Gerrit van Dyk <gerritvd@decillion.net>
|
|
Date: 1999
|
|
Author: Quentin Mathe <qmathe@club-internet.fr>
|
|
Date: 2004
|
|
|
|
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 <http://www.gnu.org/licenses/> or write to the
|
|
Free Software Foundation, 51 Franklin Street, Fifth Floor,
|
|
Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#import <Foundation/NSNotification.h>
|
|
#import <Foundation/NSString.h>
|
|
#import <Foundation/NSArray.h>
|
|
#import <Foundation/NSRunLoop.h>
|
|
#import <Foundation/NSException.h>
|
|
#import <Foundation/NSAutoreleasePool.h>
|
|
#import <Foundation/NSValue.h>
|
|
#import "AppKit/NSApplication.h"
|
|
#import "AppKit/NSBox.h"
|
|
#import "AppKit/NSBrowser.h"
|
|
#import "AppKit/NSBrowserCell.h"
|
|
#import "AppKit/NSButtonCell.h"
|
|
#import "AppKit/NSComboBox.h"
|
|
#import "AppKit/NSComboBoxCell.h"
|
|
#import "AppKit/NSEvent.h"
|
|
#import "AppKit/NSGraphicsContext.h"
|
|
#import "AppKit/NSImage.h"
|
|
#import "AppKit/NSPanel.h"
|
|
#import "AppKit/NSScreen.h"
|
|
#import "AppKit/NSScroller.h"
|
|
#import "AppKit/NSScrollView.h"
|
|
#import "AppKit/NSTableColumn.h"
|
|
#import "AppKit/NSTableView.h"
|
|
#import "AppKit/NSTextView.h"
|
|
#import "GNUstepGUI/GSTheme.h"
|
|
#import "GSGuiPrivate.h"
|
|
|
|
static NSNotificationCenter *nc;
|
|
|
|
@interface GSComboBoxTableView : NSTableView
|
|
{
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation GSComboBoxTableView
|
|
- (BOOL) acceptsFirstMouse: (NSEvent *)event
|
|
{
|
|
return YES;
|
|
}
|
|
@end
|
|
|
|
@interface GSComboWindow : NSPanel
|
|
{
|
|
NSBrowser *_browser;
|
|
GSComboBoxTableView *_tableView;
|
|
NSComboBoxCell *_cell;
|
|
BOOL _stopped;
|
|
}
|
|
|
|
+ (GSComboWindow *) defaultPopUp;
|
|
|
|
- (void) layoutWithComboBoxCell:(NSComboBoxCell *)comboBoxCell;
|
|
- (void) positionWithComboBoxCell:(NSComboBoxCell *)comboBoxCell;
|
|
- (void) popUpForComboBoxCell: (NSComboBoxCell *)comboBoxCell;
|
|
- (void) runModalPopUpWithComboBoxCell:(NSComboBoxCell *)comboBoxCell;
|
|
- (void) runLoopWithComboBoxCell:(NSComboBoxCell *)comboBoxCell;
|
|
- (void) onWindowEdited: (NSNotification *)notification;
|
|
- (void) clickItem: (id)sender;
|
|
- (void) reloadData;
|
|
- (void) noteNumberOfItemsChanged;
|
|
- (void) scrollItemAtIndexToTop: (NSInteger)index;
|
|
- (void) scrollItemAtIndexToVisible: (NSInteger)index;
|
|
- (void) selectItemAtIndex: (NSInteger)index;
|
|
- (void) deselectItemAtIndex: (NSInteger)index;
|
|
- (void) moveUpSelection;
|
|
- (void) moveDownSelection;
|
|
- (void) validateSelection;
|
|
|
|
@end
|
|
|
|
@interface NSComboBoxCell (GNUstepPrivate)
|
|
- (NSString *) _stringValueAtIndex: (NSInteger)index;
|
|
- (void) _performClickWithFrame: (NSRect)cellFrame inView: (NSView *)controlView;
|
|
- (void) _didClickWithinButton: (id)sender;
|
|
- (BOOL) _isWantedEvent: (NSEvent *)event;
|
|
- (GSComboWindow *) _popUp;
|
|
- (NSRect) _textCellFrame;
|
|
- (void) _setSelectedItem: (NSInteger)index;
|
|
- (void) _loadButtonCell;
|
|
- (void) _selectCompleted;
|
|
@end
|
|
|
|
// ---
|
|
|
|
static GSComboWindow *gsWindow = nil;
|
|
|
|
@implementation GSComboWindow
|
|
|
|
+ (GSComboWindow *) defaultPopUp
|
|
{
|
|
if (gsWindow == nil)
|
|
gsWindow = [[self alloc] initWithContentRect: NSMakeRect(0,0,200,200)
|
|
styleMask: NSBorderlessWindowMask
|
|
backing: NSBackingStoreNonretained // NSBackingStoreBuffered
|
|
defer: YES];
|
|
return gsWindow;
|
|
}
|
|
|
|
- (id) initWithContentRect: (NSRect)contentRect
|
|
styleMask: (NSUInteger)aStyle
|
|
backing: (NSBackingStoreType)bufferingType
|
|
defer: (BOOL)flag
|
|
{
|
|
NSBox *box;
|
|
NSRect borderRect;
|
|
NSScrollView *scrollView;
|
|
NSTableColumn *column;
|
|
NSCell *cell;
|
|
|
|
self = [super initWithContentRect: contentRect
|
|
styleMask: aStyle
|
|
backing: bufferingType
|
|
defer: flag];
|
|
if (nil == self)
|
|
return self;
|
|
|
|
[self setLevel: NSPopUpMenuWindowLevel];
|
|
[self setBecomesKeyOnlyIfNeeded: YES];
|
|
|
|
box = [[NSBox alloc] initWithFrame: contentRect];
|
|
[box setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
|
|
[box setBorderType: NSLineBorder];
|
|
[box setTitlePosition: NSNoTitle];
|
|
[box setContentViewMargins: NSMakeSize(0, 0)];
|
|
[self setContentView: box];
|
|
borderRect = contentRect;
|
|
RELEASE(box);
|
|
|
|
_tableView = [[GSComboBoxTableView alloc]
|
|
initWithFrame: NSMakeRect(0, 0, 100, 100)];
|
|
[_tableView setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
|
|
//[_tableView setBackgroundColor: [NSColor whiteColor]];
|
|
[_tableView setDrawsGrid: NO];
|
|
[_tableView setAllowsEmptySelection: YES];
|
|
[_tableView setAllowsMultipleSelection: NO];
|
|
[_tableView setAutoresizesAllColumnsToFit: YES];
|
|
[_tableView setHeaderView: nil];
|
|
[_tableView setCornerView: nil];
|
|
|
|
column = [[NSTableColumn alloc] initWithIdentifier: @"content"];
|
|
cell = [[NSCell alloc] initTextCell: @""];
|
|
[column setDataCell: cell];
|
|
RELEASE(cell);
|
|
[_tableView addTableColumn: column];
|
|
RELEASE(column);
|
|
|
|
[_tableView setDataSource: self];
|
|
[_tableView setDelegate: self];
|
|
[_tableView setAction: @selector(clickItem:)];
|
|
[_tableView setTarget: self];
|
|
|
|
scrollView = [[NSScrollView alloc] initWithFrame: NSMakeRect(borderRect.origin.x,
|
|
borderRect.origin.y,
|
|
borderRect.size.width,
|
|
borderRect.size.height)];
|
|
[scrollView setHasVerticalScroller: YES];
|
|
[scrollView setDocumentView: _tableView];
|
|
[_tableView release];
|
|
[box setContentView: scrollView];
|
|
RELEASE(scrollView);
|
|
|
|
[_tableView reloadData];
|
|
|
|
return self;
|
|
}
|
|
|
|
- (BOOL) canBecomeKeyWindow
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
// Browser, table view and scroll view were not retained so don't release them
|
|
[super dealloc];
|
|
}
|
|
|
|
- (void) layoutWithComboBoxCell: (NSComboBoxCell *)comboBoxCell
|
|
{
|
|
NSSize bsize = [[GSTheme theme] sizeForBorderType: NSLineBorder];
|
|
NSSize size;
|
|
CGFloat itemHeight;
|
|
CGFloat textCellWidth;
|
|
CGFloat popUpWidth;
|
|
NSSize intercellSpacing;
|
|
NSInteger num = [comboBoxCell numberOfItems];
|
|
NSInteger max = [comboBoxCell numberOfVisibleItems];
|
|
|
|
// Manage table view or browser cells height
|
|
|
|
itemHeight = [comboBoxCell itemHeight];
|
|
if (itemHeight <= 0)
|
|
{
|
|
// FIX ME : raise NSException
|
|
itemHeight = [_tableView rowHeight];
|
|
}
|
|
size.height = itemHeight;
|
|
|
|
// Manage table view or browser cells width
|
|
|
|
textCellWidth = [comboBoxCell _textCellFrame].size.width;
|
|
if ([comboBoxCell hasVerticalScroller])
|
|
{
|
|
popUpWidth = textCellWidth + [NSScroller scrollerWidth];
|
|
}
|
|
else
|
|
{
|
|
popUpWidth = textCellWidth;
|
|
}
|
|
size.width = textCellWidth - bsize.width;
|
|
|
|
if (size.width < 0)
|
|
{
|
|
size.width = 0;
|
|
}
|
|
|
|
[_tableView setRowHeight: size.height];
|
|
|
|
// Just check intercell spacing
|
|
|
|
intercellSpacing = [comboBoxCell intercellSpacing];
|
|
if (intercellSpacing.height <= 0)
|
|
{
|
|
// FIX ME : raise NSException
|
|
intercellSpacing.height = [_tableView intercellSpacing].height;
|
|
}
|
|
else
|
|
{
|
|
[_tableView setIntercellSpacing: intercellSpacing];
|
|
}
|
|
|
|
|
|
if (num > max)
|
|
num = max;
|
|
|
|
[self setFrame: [self frameRectForContentRect: NSMakeRect(0, 0, popUpWidth,
|
|
2 * bsize.height + (itemHeight + intercellSpacing.height) * (num - 1)
|
|
+ itemHeight)] display: NO];
|
|
}
|
|
|
|
- (void) positionWithComboBoxCell: (NSComboBoxCell *)comboBoxCell
|
|
{
|
|
NSView *viewWithComboCell = [comboBoxCell controlView];
|
|
NSRect screenFrame;
|
|
NSRect comboWindowFrame;
|
|
NSRect viewWithComboCellFrame;
|
|
NSRect rect;
|
|
NSPoint point, oldPoint;
|
|
|
|
[self layoutWithComboBoxCell: comboBoxCell];
|
|
|
|
// Now we can ask for the size
|
|
comboWindowFrame = [self frame];
|
|
if (comboWindowFrame.size.width == 0 || comboWindowFrame.size.height == 0)
|
|
return;
|
|
|
|
screenFrame = [[[viewWithComboCell window] screen] frame];
|
|
viewWithComboCellFrame = [comboBoxCell _textCellFrame];
|
|
if ([viewWithComboCell isFlipped])
|
|
{
|
|
point = viewWithComboCellFrame.origin;
|
|
point.y = NSMaxY(viewWithComboCellFrame);
|
|
}
|
|
else
|
|
{
|
|
point = viewWithComboCellFrame.origin;
|
|
}
|
|
|
|
// Switch to the window coordinates
|
|
point = [viewWithComboCell convertPoint: point toView: nil];
|
|
|
|
// Switch to the screen coordinates
|
|
point = [[viewWithComboCell window] convertBaseToScreen: point];
|
|
point.y -= 1 + NSHeight(comboWindowFrame);
|
|
|
|
if (point.y < 0)
|
|
{
|
|
// Off screen, so move it
|
|
oldPoint = point;
|
|
|
|
point = viewWithComboCellFrame.origin;
|
|
point.y = NSMaxY(viewWithComboCellFrame);
|
|
|
|
// Switch to the window coordinates
|
|
point = [viewWithComboCell convertPoint: point toView: nil];
|
|
|
|
// Switch to the screen coordiantes
|
|
point = [[viewWithComboCell window] convertBaseToScreen: point];
|
|
point.y += 1;
|
|
|
|
if (point.y + NSHeight(comboWindowFrame) > NSHeight(screenFrame))
|
|
point = oldPoint;
|
|
}
|
|
|
|
rect.size.width = NSWidth(comboWindowFrame);
|
|
rect.size.height = NSHeight(comboWindowFrame);
|
|
rect.origin.x = point.x;
|
|
rect.origin.y = point.y;
|
|
[self setFrame: rect display: NO];
|
|
}
|
|
|
|
- (void) popUpForComboBoxCell: (NSComboBoxCell *)comboBoxCell
|
|
{
|
|
_cell = comboBoxCell;
|
|
|
|
[self positionWithComboBoxCell: _cell];
|
|
[_cell _selectCompleted];
|
|
[self reloadData];
|
|
[self enableKeyEquivalentForDefaultButtonCell];
|
|
[self runModalPopUpWithComboBoxCell: _cell];
|
|
|
|
_cell = nil;
|
|
[self deselectItemAtIndex: 0];
|
|
}
|
|
|
|
- (void) runModalPopUpWithComboBoxCell: (NSComboBoxCell *)comboBoxCell
|
|
{
|
|
NSWindow *onWindow;
|
|
|
|
onWindow = [[_cell controlView] window];
|
|
|
|
[nc addObserver: self selector: @selector(onWindowEdited:)
|
|
name: NSWindowWillCloseNotification object: onWindow];
|
|
[nc addObserver: self selector: @selector(onWindowEdited:)
|
|
name: NSWindowWillMoveNotification object: onWindow];
|
|
[nc addObserver: self selector: @selector(onWindowEdited:)
|
|
name: NSWindowWillMiniaturizeNotification object: onWindow];
|
|
|
|
// FIX ME: The notification below doesn't exist currently
|
|
// [nc addObserver: self selector: @selector(onWindowEdited:)
|
|
// name: NSWindowWillResizeNotification object: onWindow];
|
|
|
|
|
|
// FIXME: The code below must be removed when the notifications over will work
|
|
[nc addObserver: self selector: @selector(onWindowEdited:)
|
|
name: NSWindowDidMoveNotification object: onWindow];
|
|
[nc addObserver: self selector: @selector(onWindowEdited:)
|
|
name: NSWindowDidMiniaturizeNotification object: onWindow];
|
|
[nc addObserver: self selector: @selector(onWindowEdited:)
|
|
name: NSWindowDidResizeNotification object: onWindow];
|
|
// End of the code to remove
|
|
|
|
[self orderFront: self];
|
|
[self makeFirstResponder: _tableView];
|
|
[self runLoopWithComboBoxCell: comboBoxCell];
|
|
|
|
[nc removeObserver: self name: nil object: onWindow];
|
|
|
|
[self close];
|
|
|
|
[onWindow makeFirstResponder: [_cell controlView]];
|
|
}
|
|
|
|
- (void) runLoopWithComboBoxCell: (NSComboBoxCell *)comboBoxCell
|
|
{
|
|
NSEvent *event;
|
|
NSDate *limit = [NSDate distantFuture];
|
|
unichar key;
|
|
CREATE_AUTORELEASE_POOL (pool);
|
|
|
|
while (YES)
|
|
{
|
|
event = [NSApp nextEventMatchingMask: NSAnyEventMask
|
|
untilDate: limit
|
|
inMode: NSDefaultRunLoopMode
|
|
dequeue: YES];
|
|
if ([event type] == NSLeftMouseDown
|
|
|| [event type] == NSRightMouseDown)
|
|
{
|
|
if ([comboBoxCell _isWantedEvent: event] == NO && [event window] != self)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
[NSApp sendEvent: event];
|
|
}
|
|
}
|
|
else if ([event type] == NSKeyDown)
|
|
{
|
|
key = [[event characters] characterAtIndex: 0];
|
|
if (key == NSUpArrowFunctionKey)
|
|
{
|
|
[self moveUpSelection];
|
|
}
|
|
else if (key == NSDownArrowFunctionKey)
|
|
{
|
|
[self moveDownSelection];
|
|
}
|
|
else if (key == NSNewlineCharacter
|
|
|| key == NSEnterCharacter
|
|
|| key == NSCarriageReturnCharacter)
|
|
{
|
|
[self validateSelection];
|
|
}
|
|
else if (key == 0x001b)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
[NSApp sendEvent: event];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
[NSApp sendEvent: event];
|
|
}
|
|
|
|
if (_stopped)
|
|
break;
|
|
}
|
|
|
|
_stopped = NO;
|
|
|
|
[pool drain];
|
|
}
|
|
|
|
// onWindow notifications
|
|
|
|
- (void) onWindowEdited: (NSNotification *)notification
|
|
{
|
|
_stopped = YES;
|
|
}
|
|
|
|
- (void) reloadData
|
|
{
|
|
[_tableView reloadData];
|
|
[self selectItemAtIndex: [_cell indexOfSelectedItem]];
|
|
}
|
|
|
|
- (void) noteNumberOfItemsChanged
|
|
{
|
|
// FIXME: Probably should only load the additional items
|
|
[self reloadData];
|
|
}
|
|
|
|
- (void) scrollItemAtIndexToTop: (NSInteger)index
|
|
{
|
|
NSRect rect;
|
|
|
|
rect = [_tableView frameOfCellAtColumn: 0 row: index];
|
|
[_tableView scrollPoint: rect.origin];
|
|
}
|
|
|
|
- (void) scrollItemAtIndexToVisible: (NSInteger)index
|
|
{
|
|
[_tableView scrollRowToVisible: index];
|
|
}
|
|
|
|
- (void) selectItemAtIndex: (NSInteger)index
|
|
{
|
|
if (index < 0)
|
|
return;
|
|
|
|
if ([_tableView selectedRow] == index || [_tableView numberOfRows] <= index)
|
|
return;
|
|
|
|
[_tableView selectRow: index byExtendingSelection: NO];
|
|
}
|
|
|
|
- (void) deselectItemAtIndex: (NSInteger)index
|
|
{
|
|
[_tableView deselectAll: self];
|
|
}
|
|
|
|
// Target/Action method
|
|
- (void) clickItem: (id)sender
|
|
{
|
|
if (_cell == nil)
|
|
return;
|
|
|
|
[_cell _setSelectedItem: [sender selectedRow]];
|
|
[self validateSelection];
|
|
|
|
[nc postNotificationName: NSComboBoxSelectionDidChangeNotification
|
|
object: [_cell controlView]
|
|
userInfo: nil];
|
|
}
|
|
|
|
// Browser delegate methods
|
|
- (NSInteger) browser: (NSBrowser *)sender numberOfRowsInColumn: (NSInteger)column
|
|
{
|
|
if (_cell == nil)
|
|
return 0;
|
|
|
|
return [_cell numberOfItems];
|
|
}
|
|
|
|
- (void) browser: (NSBrowser *)sender
|
|
willDisplayCell: (id)aCell
|
|
atRow: (NSInteger)row
|
|
column: (NSInteger)column
|
|
{
|
|
if (_cell == nil)
|
|
return;
|
|
|
|
[aCell setStringValue: [_cell _stringValueAtIndex: row]];
|
|
[aCell setLeaf: YES];
|
|
}
|
|
|
|
// Table view data source methods
|
|
- (NSInteger) numberOfRowsInTableView: (NSTableView *)tv
|
|
{
|
|
return [_cell numberOfItems];
|
|
}
|
|
|
|
- (id) tableView: (NSTableView *)tv objectValueForTableColumn: (NSTableColumn *)tc row: (NSInteger)row
|
|
{
|
|
return [_cell _stringValueAtIndex: row];
|
|
}
|
|
|
|
// Table view delegate methods
|
|
- (void) tableViewSelectionDidChange: (NSNotification *)notification
|
|
{
|
|
[_cell _setSelectedItem: [[notification object] selectedRow]];
|
|
|
|
[nc postNotificationName: NSComboBoxSelectionDidChangeNotification
|
|
object: [_cell controlView]
|
|
userInfo: nil];
|
|
}
|
|
|
|
// Key actions methods
|
|
- (void) moveUpSelection
|
|
{
|
|
NSInteger index = [_tableView selectedRow] - 1;
|
|
|
|
if (index > -1 && index < [_tableView numberOfRows])
|
|
{
|
|
[_tableView selectRow: index byExtendingSelection: NO];
|
|
[_tableView scrollRowToVisible: index];
|
|
}
|
|
}
|
|
|
|
- (void) moveDownSelection
|
|
{
|
|
NSInteger index = [_tableView selectedRow] + 1;
|
|
|
|
if (index > -1 && index < [_tableView numberOfRows])
|
|
{
|
|
[_tableView selectRow: index byExtendingSelection: NO];
|
|
[_tableView scrollRowToVisible: index];
|
|
}
|
|
}
|
|
|
|
- (void) validateSelection
|
|
{
|
|
if (_cell != nil)
|
|
{
|
|
NSText *textObject = nil;
|
|
id cv = [_cell controlView];
|
|
NSInteger index = [_cell indexOfSelectedItem];
|
|
|
|
if ([cv isKindOfClass: [NSControl class]])
|
|
{
|
|
textObject = [(NSControl *)cv currentEditor];
|
|
}
|
|
|
|
if (index != -1)
|
|
{
|
|
[_cell setStringValue: [_cell _stringValueAtIndex:
|
|
[_cell indexOfSelectedItem]]];
|
|
// Will update the editor when needed
|
|
}
|
|
|
|
if (textObject != nil)
|
|
{
|
|
NSRange selectionRange = NSMakeRange(0, [[textObject string] length]);
|
|
[textObject setSelectedRange: selectionRange];
|
|
[textObject scrollRangeToVisible: selectionRange];
|
|
}
|
|
|
|
[cv sendAction: [_cell action] to: [_cell target]];
|
|
|
|
_stopped = YES;
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
// ---
|
|
|
|
/**
|
|
<unit>
|
|
<heading>Class Description</heading>
|
|
<p>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 <code>setCompletes:</code> to get a suggested text
|
|
field value updated as you type. </p>
|
|
<p>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.</p>
|
|
</unit>
|
|
*/
|
|
|
|
/**
|
|
* <p>No special instructions to use NSComboBoxCell or text to detail the implementation.</p>
|
|
*/
|
|
@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 <var>flag</var> is NO and the combo cell list has more
|
|
* items (either in its default list or from its data source) than the number
|
|
* returned by <code>numberOfVisibleItems</code>, 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 <var>aSize</var>.
|
|
*/
|
|
- (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
|
|
* <var>itemHeight</var>.
|
|
*/
|
|
- (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 <code>NSComboBoxDataSource</code> 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 <var>flag</var> 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
|
|
* <var>index</var> 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
|
|
* <var>index</var> 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 <var>index</var>.
|
|
* 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 <var>index</var> 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 <code>usesDataSource</code> 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
|
|
* <code>usesDataSource</code> 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 <code>usesDataSource</code> returns NO, this method logs a warning.
|
|
*/
|
|
- (id) dataSource
|
|
{
|
|
return _dataSource;
|
|
}
|
|
|
|
/**
|
|
* Sets the combo box cell data source to <var>aSource</var>. Just calling this
|
|
* method doesn't set <code>usesDataSource</code> to return YES, you must call
|
|
* <code>setUsesDataSource:</code> 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 <var>aSource</var>
|
|
* doesn't respond to the methods <code>numberOfItemsInComboBox:</code>
|
|
* <code>comboBox:objectValueForItemAtIndex:</code>, 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
|
|
* <code>usesDataSource</code> returns NO. In the case
|
|
* <code>usesDataSource</code> 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 <code>usesDataSource</code> returns NO. In the case
|
|
* <code>usesDataSource</code> 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 <code>usesDataSource</code> returns NO. In the case
|
|
* <code>usesDataSource</code> 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 <code>usesDataSource</code> returns NO. In the case
|
|
* <code>usesDataSource</code> 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 <var>index</var> in the combo box cell
|
|
* default items list which is used when <code>usesDataSource</code> returns NO.
|
|
* In the case <code>usesDataSource</code> 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 <code>usesDataSource</code> returns NO. In the case
|
|
* <code>usesDataSource</code> 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
|
|
* <var>object</var>. In the case <code>usesDataSource</code> 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 <var>index</var> within combo box cell default
|
|
* items list. When the index is beyond the end of the list, an NSRangeException is
|
|
* raised. In the case <code>usesDataSource</code> 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
|
|
* <code>usesDataSource</code> 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 <var>object</var>, and returns
|
|
* NSNotFound when there is no such value. In the case
|
|
* <code>usesDataSource</code> 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 <var>substring</var>, or nil when there is no such string.
|
|
* <var>substring</var> 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
|
|
* <code>comboBox:completedString:</code> or <code>comboBoxCell:completedString:</code>.
|
|
* When it is the case, it uses this method to return <var>str</var>, else this
|
|
* method goes through the combo box cell items one by one and returns the first
|
|
* item found starting with <var>substring</var>.
|
|
* 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 <code>setCompletes:</code> 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 <code>completedString:</code> 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 <code>trackMouse:inRect:ofView:untilMouseUp:</code> method to establish a
|
|
* new method behavior.
|
|
* In the case <var>flag</var> 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 <var>flag</var> 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 <var>encoder</var>. 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 <var>decoder</var>. Take
|
|
* note that when the decoded instance uses a data source,
|
|
* <code>initWithCoder:<var> 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
|
|
{
|
|
id object = nil;
|
|
|
|
if (_dataSource == nil)
|
|
{
|
|
NSLog(@"%@: No data source currently specified", self);
|
|
}
|
|
else if ([_dataSource respondsToSelector:
|
|
@selector(comboBox:objectValueForItemAtIndex:)])
|
|
{
|
|
object = [_dataSource comboBox: (NSComboBox *)[self controlView]
|
|
objectValueForItemAtIndex: index];
|
|
}
|
|
else if ([_dataSource respondsToSelector:
|
|
@selector(comboBoxCell:objectValueForItemAtIndex:)])
|
|
{
|
|
object = [_dataSource comboBoxCell: self objectValueForItemAtIndex: index];
|
|
}
|
|
|
|
if (object)
|
|
{
|
|
// Check for attributed string type and return actual string instead..
|
|
if ([object isKindOfClass: [NSAttributedString class]])
|
|
{
|
|
object = [object string];
|
|
}
|
|
else
|
|
{
|
|
object = [object description];
|
|
}
|
|
|
|
// Return the request object...
|
|
return object;
|
|
}
|
|
}
|
|
|
|
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
|