/* 
   NSComboBox.m

   Control which combines a textfield and a popup list.

   Copyright (C) 2000 Free Software Foundation, Inc.

   Author:  Felipe A. Rodriguez <far@pcmagic.net>
   Date:    June 2000
   
   This file is part of the mySTEP Library and is provided
   under the terms of the GNU Library General Public License.
*/ 

#import <Foundation/NSArray.h>
#import <Foundation/NSNotification.h>
#import <Foundation/NSAutoreleasePool.h>

#import <AppKit/NSApplication.h>
#import <AppKit/NSColor.h>
#import <AppKit/NSComboBox.h>
#import <AppKit/NSButtonCell.h>
#import <AppKit/NSScrollView.h>
#import <AppKit/NSTableView.h>
#import <AppKit/NSImage.h>
#import <AppKit/NSCursor.h>

#import "NSAppKitPrivate.h"

@interface NSComboTableView : NSTableView
@end

@implementation NSComboTableView
// change behaviour as needed for a combo box
// should initWithCoder and decode
// NSDataSource
@end

@implementation NSComboBoxCell (ComboBoxCellTableViewDataSource)

- (id) tableView:(NSTableView *)aTableView
	   objectValueForTableColumn:(NSTableColumn *)aTableColumn 
	   row:(int)row
{
    return [self itemObjectValueAtIndex: row];
}

- (int) numberOfRowsInTableView:(NSTableView *)aTableView
{
    return [self numberOfItems];
}

@end


@implementation NSComboBoxCell

- (id) initTextCell:(NSString *)aString
{
	if((self=[super initTextCell:aString]))
		{
		_buttonCell=[[NSButtonCell alloc] initImageCell:[NSImage imageNamed:@"GSComboBox"]];
		[_buttonCell setAlternateImage:[NSImage imageNamed:@"GSComboBoxH"]];
		// should show different images by state + highlight while pressed
		[_buttonCell setButtonType:NSToggleButton];
		[_buttonCell setBordered:NO];
		[_buttonCell setFocusRingType:NSFocusRingTypeNone];
		_popUpList = [NSMutableArray new];
		}
	return self;
}

- (void) dealloc
{
	[_buttonCell release];
	[_popUpList release];
	[_dataSource release];
	[_tableView release];
	[super dealloc];
}

- (void) setHasVerticalScroller:(BOOL)flag	{ _cbc.hasVerticalScroller = flag;}
- (void) setIntercellSpacing:(NSSize)aSize	{ _intercellSpacing = aSize; }
- (void) setItemHeight:(float)itemHeight	{ _itemHeight = itemHeight; }
- (void) setNumberOfVisibleItems:(int)v		{ _visibleItems = v; }
- (void) setUsesDataSource:(BOOL)flag		{ _cbc.usesDataSource = flag; }
- (BOOL) usesDataSource						{ return _cbc.usesDataSource; }
- (BOOL) completes							{ return _cbc.completes; }
- (BOOL) isButtonBordered					{ return _cbc.buttonBordered; }
- (void) setCompletes:(BOOL)flag			{ _cbc.completes=flag; }
- (void) setButtonBordered:(BOOL)flag		{ _cbc.buttonBordered=flag; }

- (id) dataSource							{ return _dataSource; }

- (void) setDataSource:(id)aSource
{
	SEL a = @selector(numberOfItemsInComboBoxCell:);
	SEL b = @selector(comboBoxCell:objectValueForItemAtIndex:);

	if (!_cbc.usesDataSource)
		NSLog(@"NSComboBoxCell is not configured for an external datasource");

	if (aSource && (![aSource respondsToSelector: a] || ![aSource respondsToSelector: b]))
		NSLog(@"dataSource does not implement NSComboBoxCellDataSource protocol: %@", aSource);

	ASSIGN(_dataSource, aSource);
	if (!_tableView)
		_tableView = [[NSComboTableView alloc] initWithFrame:NSZeroRect];
	[_tableView setDataSource:self];
}

- (void) selectItemAtIndex:(int)index
{
	[_tableView selectRow:index byExtendingSelection:NO];
}

- (void) deselectItemAtIndex:(int)index
{
	[_tableView deselectRow:index];
}

- (int) indexOfSelectedItem
{
	return [_tableView selectedRow];
}

- (int) numberOfItems
{
	if (_cbc.usesDataSource)
		return [_dataSource numberOfItemsInComboBoxCell: self];

    return [_popUpList count];
}

- (int) numberOfVisibleItems			{ return _visibleItems; }
- (BOOL) hasVerticalScroller			{ return YES; }
- (NSSize) intercellSpacing				{ return _intercellSpacing; }
- (float) itemHeight					{ return _itemHeight; }

- (void) addItemWithObjectValue:(id)object
{
	if (_cbc.usesDataSource)
		NSLog(@"NSComboBoxCell is not configured for an internal datasource");
	else
		[_popUpList addObject:object];
}

- (void) addItemsWithObjectValues:(NSArray *)objects
{
	if (_cbc.usesDataSource)
		NSLog(@"NSComboBoxCell is not configured for an internal datasource");
	else
		[_popUpList addObjectsFromArray: objects];
}

- (void) insertItemWithObjectValue:(id)object atIndex:(int)index
{
	if (_cbc.usesDataSource)
		NSLog(@"NSComboBoxCell is not configured for an internal datasource");
	else
		[_popUpList insertObject:object atIndex:index];
}

- (void) removeItemWithObjectValue:(id)object
{
	if (_cbc.usesDataSource)
		NSLog(@"NSComboBoxCell is not configured for an internal datasource");
	else
		[_popUpList removeObjectIdenticalTo:object];
}

- (void) removeItemAtIndex:(int)index
{ 
	if (_cbc.usesDataSource)
		NSLog(@"NSComboBoxCell is not configured for an internal datasource");
	else
		[_popUpList removeObjectAtIndex:index];
}

- (void) removeAllItems
{
	if (_cbc.usesDataSource)
		NSLog(@"NSComboBoxCell is not configured for an internal datasource");
	else
		[_popUpList removeAllObjects];
}

- (void) selectItemWithObjectValue:(id)object
{
	[self selectItemAtIndex: [self indexOfItemWithObjectValue:object]];
}

- (id) itemObjectValueAtIndex:(int)index
{
	if (_cbc.usesDataSource)
		return [_dataSource comboBoxCell:self objectValueForItemAtIndex:index];

    return [_popUpList objectAtIndex:index];
}

- (id) objectValueOfSelectedItem
{
	return [self itemObjectValueAtIndex: [_tableView selectedRow]];
}

- (int) indexOfItemWithObjectValue:(id)object
{
	if (_cbc.usesDataSource)
		NSLog(@"NSComboBoxCell is not configured for an internal datasource");
	else
		return [_popUpList indexOfObject:object];
	
	return NSNotFound;
}

- (void) reloadData							{ [_tableView reloadData]; }

- (void) noteNumberOfItemsChanged
{
	[self reloadData];
}

- (void) scrollItemAtIndexToTop:(int)index
{
}

- (void) scrollItemAtIndexToVisible:(int)index
{
	NIMP;
}

- (NSArray *) objectValues					{ return _popUpList; }

- (void) _popUp:(NSRect) t cellFrame:(NSRect) cellFrame;
{
	NSPoint o = [_controlView convertPoint:t.origin toView:nil];
	NSPoint l = [[_controlView window] convertBaseToScreen: o];	
#if 1
	NSLog(@"pop up");
#endif
	l.y -= 100;
	if(!_popUpWindow)
		{ // create a new window
		NSRect f = {l,{NSWidth(cellFrame),100}};
		NSTableColumn *tc;
		
		_popUpWindow = [[NSWindow alloc] initWithContentRect:f
												   styleMask:NSBorderlessWindowMask
													 backing:NSBackingStoreRetained
													   defer:YES
													  screen:nil];
		
		f.origin = NSZeroPoint;
		if (!_tableView)
			{ // create a fresh one
			_tableView = [[NSComboTableView alloc] initWithFrame:f];
			[_tableView setBackgroundColor:[NSColor whiteColor]];
			[_tableView setDataSource: self];
			[_tableView setAllowsColumnReordering:NO];
			[_tableView setAllowsColumnResizing:NO];
			[_tableView setColumnAutoresizingStyle:NSTableViewUniformColumnAutoresizingStyle];
#if 0
			NSLog(@"table view: %@", [_tableView _descriptionWithSubviews]);
#endif
			tc = [[NSTableColumn alloc] initWithIdentifier:@"ComboBoxCell"];
			[tc setWidth:NSWidth(t)];
#if 1
			NSLog(@"table column: %@", tc);
#endif
			[_tableView addTableColumn:tc];
			}
		else
			{
			[_tableView setFrame:f];	// simply adjust - it should autosize
#if 1	// not yet...
			[[[_tableView tableColumns] objectAtIndex:0] setWidth:NSWidth(t)];	// make as wide as the text field
#endif
			}
#if 0
		NSLog(@"table columns: %@", [_tableView tableColumns]);
#endif
		[_tableView setHeaderView:nil];	// no header
		[_tableView setTarget:_controlView];
		[_tableView setAction:@selector(takeObjectValueFrom:)];
		_scrollView = [[NSScrollView alloc] initWithFrame:f];
		[_scrollView setHasHorizontalScroller:YES];
		[_scrollView setHasVerticalScroller:YES];
		[_scrollView setDocumentView:_tableView];	// embed NSTableView
#if 0
		NSLog(@"scroll view: %@", [_scrollView _descriptionWithSubviews]);
#endif
		[[_popUpWindow contentView] addSubview:_scrollView];
#if 0
		NSLog(@"popup content view: %@", [[_popUpWindow contentView] _descriptionWithSubviews]);
#endif
		}
	else
		[_popUpWindow setFrameOrigin: l];	// just reposition
	
	[_tableView reloadData];
	[_popUpWindow makeKeyAndOrderFront:self];
	[_buttonCell setState:NSOnState];
}

- (void) _popDown;
{
#if 1
	NSLog(@"pop down");
#endif
	[_buttonCell setState:NSOffState];
	[_popUpWindow orderOut:self];
}

- (BOOL) _isPoppedUp;
{
	return _popUpWindow && [_popUpWindow isVisible];
}

// FIXME: doc says this method is generally not overridden!

- (BOOL) trackMouse:(NSEvent *)event
			 inRect:(NSRect)cellFrame
			 ofView:(NSView *)controlView
			 untilMouseUp:(BOOL)flag
{
	NSWindow *w = [controlView window];
	BOOL r;
	NSRect b, t;
	NSPoint p;

	if(_c.editing)
		[self endEditing:[w fieldEditor:NO forObject:self]];

	_controlView=controlView;
	
	p = [controlView convertPoint:[event locationInWindow] fromView:nil];

	NSDivideRect(cellFrame, &b, &t, 22, NSMaxXEdge);
	
	if((NSMouseInRect(p, t, YES)))
		{ // forward click event to the field editor
		return [super trackMouse:event inRect:cellFrame ofView:controlView untilMouseUp:flag];
		}
	b = NSInsetRect(b, 2, 2);
	[controlView lockFocus];
	while (YES)
		{ // normal button&outside event tracking
		[_buttonCell highlight:YES withFrame:b inView:controlView];	
		[w flushWindow];
		
		if ((r = [_buttonCell trackMouse:event
								  inRect:b
								  ofView:controlView
							untilMouseUp:YES]))
			{ // track within button
			if([self _isPoppedUp])
				{
				[self _popDown];	// second click
				[_buttonCell highlight:YES withFrame:b inView:controlView];	
				}
			else
				{
				[self _popUp:t cellFrame:cellFrame];
				[_buttonCell highlight:YES withFrame:b inView:controlView];		// draw new state
				while (YES)
					{ // track within subwindow
					NSAutoreleasePool *pool = [NSAutoreleasePool new];
					NSWindow *ew;
					NSEvent *e;
					
					e = [NSApp nextEventMatchingMask:NSAnyEventMask
										   untilDate:[NSDate distantFuture]				
											  inMode:NSModalPanelRunLoopMode
											 dequeue:YES];
					
					if ((ew = [e window]) == _popUpWindow)
						{ // send to the NSTableView
						[_popUpWindow sendEvent: e];
						if([e type] == NSLeftMouseDown)
							e = nil;
						}
					else						// mouse not in popUpWindow
						{						// see if we need to abort
						NSEventType et = [e type];
						
						if ((et != NSMouseMoved && et != NSSystemDefined
							 && et != NSAppKitDefined) || (et == NSSystemDefined && (ew == w)))
							{
							BOOL repost = YES;	// NSSystemDefined informs
												// us control view window
							if (ew == w)		// has lost focus
								{
								NSPoint z = [e locationInWindow];		
								z = [controlView convertPoint:z fromView:nil];
								if(NSMouseInRect(z, b, YES))
									repost = NO;
								}				// do not repost if mouse
												// down in our button cell
							if (repost)
								[NSApp postEvent:e atStart:YES];
							e = nil;
							}
						}
					
					[pool release];
					if (e == nil)
						break;
					}
				
				[self _popDown];	// end of tracking in popped up window
				}
			break;
			}
		else
			{
			[_buttonCell highlight:NO withFrame:b inView:controlView];
			[w flushWindow];
			}
	
		event = [NSApp nextEventMatchingMask:GSTrackingLoopMask
							   untilDate:[NSDate distantFuture]
								  inMode:NSEventTrackingRunLoopMode 
								 dequeue:YES];
		
		if ([event type] == NSLeftMouseUp)
			break;
		}
	
	[self _popDown];	// finally pop down (if not yet)
	[_buttonCell highlight:NO withFrame:b inView:controlView];	// restore default button state (off, not highlighted)
	[w flushWindow];
	[controlView unlockFocus];
	return r;
}

- (void) drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView
{
	NSRect b, t;
	_controlView = controlView;							// last view drawn in
	NSDivideRect(cellFrame, &b, &t, 22, NSMaxXEdge);
	[super drawInteriorWithFrame:t inView:controlView];		// text part to the left
	[_buttonCell drawWithFrame:b inView:controlView];		// full button (incl. bezel) to the right
}

- (void) encodeWithCoder:(id)aCoder						// NSCoding protocol
{
	[super encodeWithCoder:aCoder];
}

- (id) initWithCoder:(id)aDecoder
{
#if 0
	NSLog(@"%@ initWithCoder:%@", NSStringFromClass([self class]), aDecoder);
#endif
	if((self=[super initWithCoder:aDecoder]))
		{
		_buttonCell=[[NSButtonCell alloc] initImageCell:[NSImage imageNamed:@"GSComboBox"]];
		[_buttonCell setAlternateImage:[NSImage imageNamed:@"GSComboBoxH"]];
		[_buttonCell setButtonType:NSToggleButton];
		[_buttonCell setBordered:NO];
		[_buttonCell setFocusRingType:NSFocusRingTypeNone];
		_cbc.usesDataSource = [aDecoder decodeBoolForKey:@"NSUsesDataSource"];
		_cbc.completes = [aDecoder decodeBoolForKey:@"NSCompletes"];
		if([aDecoder containsValueForKey:@"NSDataSource"])
			[self setDataSource:[aDecoder decodeObjectForKey:@"NSDataSource"]];	// this allocates new tableView if needed
		if([aDecoder containsValueForKey:@"NSPopUpListData"])
			_popUpList=[[aDecoder decodeObjectForKey:@"NSPopUpListData"] retain];
		else
			_popUpList = [NSMutableArray new];
		_cbc.hasVerticalScroller = [aDecoder decodeBoolForKey:@"NSHasVerticalScroller"];
		_visibleItems = [aDecoder decodeIntForKey:@"NSVisibleItemCount"];
		_tableView=[[aDecoder decodeObjectForKey:@"NSTableView"] retain];	// use this preinitialized one (should be a NSComboTableView)
		// _intercellSpacing = aSize;
		// _itemHeight = itemHeight;
		}
	return self;
}

- (NSString *) completedString:(NSString *) substring;
{
	NSEnumerator *e;
	NSString *val;
	if(_cbc.usesDataSource)
		{
		if([_dataSource respondsToSelector:@selector(comboBoxCell:completedString:)])
			return [_dataSource comboBoxCell:self completedString:substring];
		return nil;
		}
	e=[_popUpList objectEnumerator];
	while((val=[e nextObject]))
		if([val hasPrefix:substring])
			return val;	// found
	return nil;
}

@end /* NSComboBoxCell */

// class variables
static Class __comboBoxCellClass = Nil;

@implementation NSComboBox

+ (void) initialize
{
	__comboBoxCellClass = [NSComboBoxCell class];
}

+ (Class) cellClass							{ return __comboBoxCellClass?__comboBoxCellClass:[super cellClass]; }
+ (void) setCellClass:(Class)class			{ __comboBoxCellClass = class; }

#if OLD

- (id) initWithFrame:(NSRect)frameRect
{
	self=[super initWithFrame:frameRect];
	if(self)
		{
		[self setCell: [[NSComboBoxCell new] autorelease]];
		}
	return self;
}
#endif

- (void) dealloc
{
	[self setDelegate:nil];
	[super dealloc];
}

// pass all to controlled cell

- (void) setHasVerticalScroller:(BOOL)f	{ [_cell setHasVerticalScroller:f]; }
- (void) setIntercellSpacing:(NSSize)s	{ [_cell setIntercellSpacing:s]; }
- (void) setItemHeight:(float)h			{ [_cell setItemHeight:h]; }
- (void) setNumberOfVisibleItems:(int)v	{ [_cell setNumberOfVisibleItems:v]; }
- (BOOL) hasVerticalScroller			{ return [_cell hasVerticalScroller]; }
- (NSSize) intercellSpacing				{ return [_cell intercellSpacing]; }
- (float) itemHeight					{ return [_cell itemHeight]; }
- (int) numberOfVisibleItems			{ return [_cell numberOfVisibleItems];}
- (void) reloadData						{ [_cell reloadData]; }
- (void) noteNumberOfItemsChanged		{ [_cell noteNumberOfItemsChanged]; }
- (void) scrollItemAtIndexToTop:(int)index	{ [_cell scrollItemAtIndexToTop:index]; }
- (void) scrollItemAtIndexToVisible:(int)index	{ [_cell scrollItemAtIndexToVisible:index]; }
- (void) selectItemAtIndex:(int)index	{ [_cell selectItemAtIndex:index]; }
- (void) deselectItemAtIndex:(int)index	{ [_cell deselectItemAtIndex:index]; }
- (int) indexOfSelectedItem				{ return [_cell indexOfSelectedItem]; }
- (int) numberOfItems					{ return [_cell numberOfItems]; }
- (void) setUsesDataSource:(BOOL)flag	{ [_cell setUsesDataSource:flag]; }
- (BOOL) usesDataSource					{ return [_cell usesDataSource]; }
- (BOOL) completes						{ return [_cell completes]; }
- (BOOL) isButtonBordered				{ return [_cell isButtonBordered]; }
- (void) setCompletes:(BOOL)flag		{ [_cell setCompletes:flag]; }
- (void) setButtonBordered:(BOOL)flag	{ [_cell setButtonBordered:flag]; }

- (id) dataSource						{ return _dataSource; }

- (void) setDataSource:(id)aSource
{
	SEL a = @selector(numberOfItemsInComboBox:);
	SEL b = @selector(comboBox:objectValueForItemAtIndex:);
	
	if (![_cell usesDataSource])
		NSLog(@"NSComboBox is not configured for an external datasource");
	
	if(aSource && (![aSource respondsToSelector: a] || ![aSource respondsToSelector: b]))
		NSLog(@"dataSource does not implement NSComboBoxDataSource protocol: %@", aSource);
	
	ASSIGN(_dataSource, aSource);
	[_cell setDataSource:self];	// make us the data source of our cell so that we can translate all data source calls
}

- (void) addItemWithObjectValue:(id)o	{ [_cell addItemWithObjectValue:o]; }

- (void) addItemsWithObjectValues:(NSArray *)objects
{
	[_cell addItemsWithObjectValues:objects];
	[self setNeedsDisplay: YES];
}

- (void) insertItemWithObjectValue:(id)object atIndex:(int)index
{
	[_cell insertItemWithObjectValue:object atIndex:index];
	[self setNeedsDisplay: YES];
}

- (void) removeItemWithObjectValue:(id)o{ [_cell removeItemWithObjectValue:o];}
- (void) removeItemAtIndex:(int)index	{ [_cell removeItemAtIndex:index]; }
- (void) removeAllItems					{ [_cell removeAllItems]; }
- (NSArray *) objectValues				{ return [_cell objectValues]; }
- (void) selectItemWithObjectValue:(id)object	{ [_cell selectItemWithObjectValue:object]; }
- (id) itemObjectValueAtIndex:(int)index	{ return [_cell itemObjectValueAtIndex:index]; }
- (id) objectValueOfSelectedItem		{ return [_cell objectValueOfSelectedItem]; }
- (int) indexOfItemWithObjectValue:(id)object	{ return [_cell indexOfItemWithObjectValue:object]; }

#if OLD
- (void) mouseDown:(NSEvent*)event
{
	if (![_cell trackMouse:event inRect:bounds ofView:self untilMouseUp:YES])
		{ // if not tracked by the NSComboBoxCell - forward event to the field editor
		NSRect b, t, d = [_cell drawingRectForBounds:bounds];

		NSDivideRect(d, &b, &t, 22, NSMaxXEdge);
		[_cell editWithFrame:t 			
			   inView:self				
			   editor:[window fieldEditor:YES forObject:_cell]	
			   delegate:self	
			   event:event];
		}
}
#endif

- (void) setDelegate:(id)anObject
{
NSNotificationCenter *n;

	if(_delegate == anObject)
		return;

#define IGNORE_(notif_name) [n removeObserver:_delegate \
								name:NSComboBox##notif_name##Notification \
								object:self]

	n = [NSNotificationCenter defaultCenter];
	if (_delegate)
		{
		IGNORE_(SelectionIsChanging);
		IGNORE_(SelectionDidChange);
		IGNORE_(WillDismiss);
		IGNORE_(WillPopUp);
		}

	[super setDelegate:anObject];
	// ASSIGN(_delegate, anObject);
	if(anObject)
		{

#define OBSERVE_(notif_name) \
	if ([_delegate respondsToSelector:@selector(comboBox##notif_name:)]) \
		[n addObserver:_delegate \
		   selector:@selector(comboBox##notif_name:) \
		   name:NSComboBox##notif_name##Notification \
		   object:self]

		OBSERVE_(SelectionIsChanging);
		OBSERVE_(SelectionDidChange);
		OBSERVE_(WillDismiss);
		OBSERVE_(WillPopUp);
		}
}

- (void) resetCursorRects								// Manage the cursor
{
	NSRect b, t;
	NSDivideRect(bounds, &b, &t, 22, NSMaxXEdge);
	[self addCursorRect:t cursor:[NSCursor IBeamCursor]];
}

- (void) takeObjectValueFrom:(id)sender					// override NSControl
{
	[_cell setObjectValue: [_cell objectValueOfSelectedItem]];
}

// translate cell dataSource methods

- (NSString *)comboBoxCell:(NSComboBoxCell *)aComboBoxCell completedString:(NSString *)uncompletedString;
{
	if(aComboBoxCell == _cell)
		return [_dataSource comboBox:self completedString:uncompletedString];
	return uncompletedString;
}

- (unsigned int)comboBoxCell:(NSComboBoxCell *)aComboBoxCell indexOfItemWithStringValue:(NSString *)aString
{
	if(aComboBoxCell == _cell)
		return [_dataSource comboBox:self indexOfItemWithStringValue:aString];
	return NSNotFound;
}

- (id)comboBoxCell:(NSComboBoxCell *)aComboBoxCell objectValueForItemAtIndex:(int)index
{
	if(aComboBoxCell == _cell)
		return [_dataSource comboBox:self objectValueForItemAtIndex:index];
	return nil;
}

- (int)numberOfItemsInComboBoxCell:(NSComboBoxCell *)aComboBoxCell
{
	if(aComboBoxCell == _cell)
		return [_dataSource numberOfItemsInComboBox:self];
	return 0;
}

- (id) initWithCoder:(id)aDecoder
{
#if 0
	NSLog(@"%@ initWithCoder:%@", NSStringFromClass([self class]), aDecoder);
#endif
	if((self=[super initWithCoder:aDecoder]))
		{
		[self setDataSource:[aDecoder decodeObjectForKey:@"NSDataSource"]];
		}
#if 0
	NSLog(@"%@ initWithCoder:%@ -> %@", NSStringFromClass([self class]), aDecoder, self);
#endif
	return self;
}

@end /* NSComboBox */
