mirror of
https://github.com/gnustep/libs-gui.git
synced 2025-04-25 06:30:55 +00:00
functionality change in non-GC mode, in GC mode it invokes a quick GC pass to try to delete the short-lived objects. Also deleted some [pool release] lines just before exit() or return-from-main statements. These cause objects to be swapped in and destructors to be run to no benefit (the OS will reclaim this memory without requiring stuff to be swapped in when the process exits). git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@33146 72102866-910b-0410-8b05-ffd578937521
1884 lines
51 KiB
Objective-C
1884 lines
51 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: (int)index;
|
|
- (void) scrollItemAtIndexToVisible: (int)index;
|
|
- (void) selectItemAtIndex: (int)index;
|
|
- (void) deselectItemAtIndex: (int)index;
|
|
- (void) moveUpSelection;
|
|
- (void) moveDownSelection;
|
|
- (void) validateSelection;
|
|
|
|
@end
|
|
|
|
@interface NSComboBoxCell (GNUstepPrivate)
|
|
- (NSString *) _stringValueAtIndex: (int)index;
|
|
- (void) _performClickWithFrame: (NSRect)cellFrame inView: (NSView *)controlView;
|
|
- (void) _didClickWithinButton: (id)sender;
|
|
- (BOOL) _isWantedEvent: (NSEvent *)event;
|
|
- (GSComboWindow *) _popUp;
|
|
- (NSRect) _textCellFrame;
|
|
- (void) _setSelectedItem: (int)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: (unsigned int)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];
|
|
[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;
|
|
float itemHeight;
|
|
float textCellWidth;
|
|
float popUpWidth;
|
|
NSSize intercellSpacing;
|
|
int num = [comboBoxCell numberOfItems];
|
|
int 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: (int)index
|
|
{
|
|
NSRect rect;
|
|
|
|
rect = [_tableView frameOfCellAtColumn: 0 row: index];
|
|
[_tableView scrollPoint: rect.origin];
|
|
}
|
|
|
|
- (void) scrollItemAtIndexToVisible: (int)index
|
|
{
|
|
[_tableView scrollRowToVisible: index];
|
|
}
|
|
|
|
- (void) selectItemAtIndex: (int)index
|
|
{
|
|
if (index < 0)
|
|
return;
|
|
|
|
if ([_tableView selectedRow] == index || [_tableView numberOfRows] <= index)
|
|
return;
|
|
|
|
[_tableView selectRow: index byExtendingSelection: NO];
|
|
}
|
|
|
|
- (void) deselectItemAtIndex: (int)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
|
|
- (int) browser: (NSBrowser *)sender numberOfRowsInColumn: (int)column
|
|
{
|
|
if (_cell == nil)
|
|
return 0;
|
|
|
|
return [_cell numberOfItems];
|
|
}
|
|
|
|
- (void) browser: (NSBrowser *)sender
|
|
willDisplayCell: (id)aCell
|
|
atRow: (int)row
|
|
column: (int)column
|
|
{
|
|
if (_cell == nil)
|
|
return;
|
|
|
|
[aCell setStringValue: [_cell _stringValueAtIndex: row]];
|
|
[aCell setLeaf: YES];
|
|
}
|
|
|
|
// Table view data source methods
|
|
- (int) numberOfRowsInTableView: (NSTableView *)tv
|
|
{
|
|
return [_cell numberOfItems];
|
|
}
|
|
|
|
- (id) tableView: (NSTableView *)tv objectValueForTableColumn: (NSTableColumn *)tc row: (int)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
|
|
{
|
|
int index = [_tableView selectedRow] - 1;
|
|
|
|
if (index > -1 && index < [_tableView numberOfRows])
|
|
{
|
|
[_tableView selectRow: index byExtendingSelection: NO];
|
|
[_tableView scrollRowToVisible: index];
|
|
}
|
|
}
|
|
|
|
- (void) moveDownSelection
|
|
{
|
|
int 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];
|
|
int 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.
|
|
*/
|
|
- (float) itemHeight
|
|
{
|
|
return _itemHeight;
|
|
}
|
|
|
|
/**
|
|
* Sets the height of the items in the combo box cell list to
|
|
* <var>itemHeight</var>.
|
|
*/
|
|
- (void) setItemHeight: (float)itemHeight
|
|
{
|
|
if (itemHeight > 14)
|
|
_itemHeight = itemHeight;
|
|
}
|
|
|
|
/**
|
|
* Returns the maximum number of allowed items to be displayed in the combo box
|
|
* cell list.
|
|
*/
|
|
- (int) numberOfVisibleItems
|
|
{
|
|
return _visibleItems;
|
|
}
|
|
|
|
/**
|
|
* Sets the maximum number of allowed items to be displayed in the combo box
|
|
* cell list.
|
|
*/
|
|
- (void) setNumberOfVisibleItems: (int)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: (int)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: (int)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: (int)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: (int)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.
|
|
*/
|
|
- (int) 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.
|
|
*/
|
|
- (int) 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: (int)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: (int)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
|
|
{
|
|
int 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: (int)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
|
|
{
|
|
int 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
|
|
{
|
|
int 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.
|
|
*/
|
|
- (int) 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 == NO)
|
|
{
|
|
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
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < [self numberOfItems]; i++)
|
|
{
|
|
NSString *str = [self _stringValueAtIndex: i];
|
|
|
|
if ([str length] > [substring length] && [str hasPrefix: substring])
|
|
return str;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
unsigned int 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;
|
|
unsigned int 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 encodeInt: [self numberOfVisibleItems] forKey: @"NSVisibleItemCount"];
|
|
}
|
|
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];
|
|
[coder encodeValueOfObjCType: @encode(int) at: &_visibleItems];
|
|
[coder encodeValueOfObjCType: @encode(NSSize) at: &_intercellSpacing];
|
|
[coder encodeValueOfObjCType: @encode(float) at: &_itemHeight];
|
|
[coder encodeValueOfObjCType: @encode(int) at: &_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"];
|
|
// FIXME: This does not match the way GNUstep currently implements
|
|
// the list of popup items.
|
|
//id table = [aDecoder decodeObjectForKey: @"NSTableView"];
|
|
|
|
if ([aDecoder containsValueForKey: @"NSHasVerticalScroller"])
|
|
{
|
|
[self setHasVerticalScroller: [aDecoder decodeBoolForKey:
|
|
@"NSHasVerticalScroller"]];
|
|
}
|
|
if ([aDecoder containsValueForKey: @"NSVisibleItemCount"])
|
|
{
|
|
[self setNumberOfVisibleItems: [aDecoder decodeIntForKey:
|
|
@"NSVisibleItemCount"]];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BOOL dummy;
|
|
id previouslyEncodedButton;
|
|
|
|
if ([aDecoder versionForClassName: @"NSComboBoxCell"] < 2)
|
|
[aDecoder decodeValueOfObjCType: @encode(id) at: &previouslyEncodedButton];
|
|
// In previous version we decode _buttonCell, we just discard the decoded value here
|
|
|
|
[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];
|
|
[aDecoder decodeValueOfObjCType: @encode(int) at: &_visibleItems];
|
|
[aDecoder decodeValueOfObjCType: @encode(NSSize) at: &_intercellSpacing];
|
|
[aDecoder decodeValueOfObjCType: @encode(float) at: &_itemHeight];
|
|
[aDecoder decodeValueOfObjCType: @encode(int) at: &_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: (int)selStart
|
|
length: (int)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;
|
|
unsigned int myStringLength = [myString length];
|
|
unsigned int location, length;
|
|
NSRange selectedRange = [textObject selectedRange];
|
|
|
|
if (myStringLength != 0
|
|
&& selectedRange.location == myStringLength
|
|
&& _prevSelectedRange.location < selectedRange.location)
|
|
{
|
|
more = [self completedString: myString];
|
|
if ([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: (int)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: (int)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:)];
|
|
}
|
|
|
|
- (void) _selectCompleted
|
|
{
|
|
NSString *more;
|
|
unsigned int 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
|