/** <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 Library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this library; see the file COPYING.LIB.
   If not, write to the Free Software Foundation,
   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include <Foundation/NSNotification.h>
#include <Foundation/NSString.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSRunLoop.h>
#include <Foundation/NSException.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSValue.h>
#include "AppKit/NSApplication.h"
#include "AppKit/NSBox.h"
#include "AppKit/NSBrowser.h"
#include "AppKit/NSBrowserCell.h"
#include "AppKit/NSButtonCell.h"
#include "AppKit/NSComboBox.h"
#include "AppKit/NSComboBoxCell.h"
#include "AppKit/NSGraphicsContext.h"
#include "AppKit/NSImage.h"
#include "AppKit/NSMatrix.h"
#include "AppKit/NSPanel.h"
#include "AppKit/NSScreen.h"
#include "AppKit/NSScroller.h"
#include "AppKit/NSScrollView.h"
#include "AppKit/NSTableColumn.h"
#include "AppKit/NSTableView.h"
#include "AppKit/NSTextView.h"

static NSNotificationCenter *nc;
static const BOOL ForceBrowser = NO;

@interface GSFirstMouseTableView : NSTableView
{

}

@end

@implementation GSFirstMouseTableView
- (BOOL) acceptsFirstMouse: (NSEvent *)event
{
  return YES;
}
@end

@interface GSComboWindow : NSPanel
{
   NSBrowser *_browser;
   GSFirstMouseTableView *_tableView;
   NSComboBoxCell *_cell;
   BOOL _stopped;
   BOOL _localSelection;
}

+ (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) 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)
    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;
  NSScrollView *scrollView;
  NSRect borderRect;
   
  self = [super initWithContentRect: contentRect
		          styleMask: aStyle
		            backing: bufferingType
		              defer: flag];	  
  [self setLevel: NSPopUpMenuWindowLevel];
  [self setBecomesKeyOnlyIfNeeded: YES];
  
  _localSelection = NO;
  
  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);
  
  if (!ForceBrowser)
    {
  _tableView = [[GSFirstMouseTableView 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];
  [_tableView addTableColumn: [[NSTableColumn alloc] initWithIdentifier: @"content"]];
  [[_tableView tableColumnWithIdentifier:@"content"]  setDataCell: [[NSCell alloc] initTextCell: @""]];
  [_tableView setDataSource: self];
  [_tableView setDelegate: 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];
  
  [_tableView reloadData];
    }
  else
    {
  _browser = [[NSBrowser alloc] initWithFrame: NSMakeRect(borderRect.origin.x, 
                                                          borderRect.origin.y,
				                        borderRect.size.width, 
						     borderRect.size.height)];
  [_browser setMaxVisibleColumns: 1];
  [_browser setTitled: NO];
  [_browser setHasHorizontalScroller: NO];
  [_browser setTarget: self];
  [_browser setAction: @selector(selectItem:)];
  [_browser setDelegate: self];
  [_browser setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
  [_browser setAllowsEmptySelection: YES];
  [_browser setAllowsMultipleSelection: NO];
  [_browser setReusesColumns: YES];
  // Create an empty matrix
  [_browser loadColumnZero];
  [box setContentView: _browser];
    }

  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
{
  NSMatrix *matrix = [_browser matrixInColumn: 0];
  NSSize bsize = _sizeForBorderType(NSLineBorder);
  NSSize size;
  float itemHeight;
  float textCellWidth;
  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
      if (!ForceBrowser)
        {
      itemHeight = [_tableView rowHeight];
        }
      else
        {
      itemHeight = [matrix cellSize].height;
        }
    }
  size.height = itemHeight;
    
  // Manage table view or browser cells width
  
  textCellWidth = [comboBoxCell _textCellFrame].size.width;
  if ([comboBoxCell hasVerticalScroller])
    {
      size.width = textCellWidth - [NSScroller scrollerWidth] - bsize.width;
    }
  else 
    {
      size.width = textCellWidth - bsize.width;
    }
  if (size.width < 0)
    {
      size.width = 0;
    }
   
  if (!ForceBrowser)
    {
  [_tableView setRowHeight: size.height];
    }
  else
    {
  [matrix setCellSize: size];
    }
    
  // Just check intercell spacing

  intercellSpacing = [comboBoxCell intercellSpacing];
  if (intercellSpacing.height <= 0)
    {
      // FIX ME : raise NSException
      if (!ForceBrowser)
        {
      intercellSpacing.height = [_tableView intercellSpacing].height;
        }
      else
        {
      intercellSpacing.height = [matrix intercellSpacing].height;
	}
    }
  else 
    {
      if (!ForceBrowser)
        {
      [_tableView setIntercellSpacing: intercellSpacing];
        }
      else
        {
      [matrix setIntercellSpacing: intercellSpacing];
        }
    }
    
    
  if (num > max)
    num = max;
   
  [self setFrame: NSMakeRect(0, 0, textCellWidth, 
     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;
  NSView *superview = [viewWithComboCell superview];
   
  [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 = [viewWithComboCell frame];
  
  point = viewWithComboCellFrame.origin;
  
  // Switch to the window coordinates
  point = [[viewWithComboCell superview] convertPoint: point toView: nil];
  
  // Switch to the screen coordinates
  point = [[viewWithComboCell window] convertBaseToScreen: point];
  
  // Take in account flipped view
  if ([superview isFlipped])
    point.y += NSHeight([superview frame]) 
      - (viewWithComboCellFrame.origin.y * 2 + NSHeight(viewWithComboCellFrame));
  
  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 superview] convertPoint: point toView: nil];
  
      // Switch to the screen coordiantes
      point = [[viewWithComboCell window] convertBaseToScreen: point];
        
      // Take in account flipped view
      if ([superview isFlipped])
        point.y += NSHeight([superview frame]) 
          - (viewWithComboCellFrame.origin.y * 2 + NSHeight(viewWithComboCellFrame));
	
      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: NSWindowWillMoveNotification object: onWindow];
  [nc addObserver: self selector: @selector(onWindowEdited:) 
    name: NSWindowWillMiniaturizeNotification object: onWindow];
  /* the notification below doesn't exist currently
  [nc addObserver: self selector: @selector(onWindowEdited:) 
    name: NSWindowWillResizeNotification object: onWindow];
  */
  [nc addObserver: self selector: @selector(onWindowEdited:) 
    name: NSWindowWillCloseNotification object: onWindow];
   
  // ### Hack 
  // ### 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];
  // ###
  
  [self orderFront: self];
  [self makeFirstResponder: _tableView];
  [self runLoopWithComboBoxCell: comboBoxCell];
  
  [nc removeObserver: self];
  [_tableView setDelegate: self];
  // Hack
  // Need to reset the delegate to receive the next notifications
  
  [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] && [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
            {
	      [NSApp sendEvent: event];
	    }
	}
      else
        {
	  [NSApp sendEvent: event];
	}
      
      if (_stopped)
        break;      
    }
    
  _stopped = NO;
    
  RELEASE(pool);
}

// onWindow notifications

- (void) onWindowEdited: (NSNotification *)notification
{
  _stopped = YES;
}

- (void) reloadData
{
  if (!ForceBrowser)
    {
  [_tableView reloadData];
    }
  else
    {
  [_browser loadColumnZero];
    }
  [self selectItemAtIndex: [_cell indexOfSelectedItem]];
}

- (void) noteNumberOfItemsChanged
{
  // FIXME: Should only load the additional items
  [self reloadData];
}

- (void) scrollItemAtIndexToTop: (int)index
{
  NSRect rect;
  
  if (!ForceBrowser)
    {
  rect = [_tableView frameOfCellAtColumn: 0 row: index];
  [_tableView scrollPoint: rect.origin]; 
    }
  else
    {  
  NSMatrix *matrix = [_browser matrixInColumn: 0];

  rect = [matrix cellFrameAtRow: index column: 0];
  [matrix scrollPoint: rect.origin];
    }
}

- (void) scrollItemAtIndexToVisible: (int)index
{
  if (!ForceBrowser)
    {
  [_tableView scrollRowToVisible: index];
    }
  else
    {
  NSMatrix *matrix = [_browser matrixInColumn: 0];

  [matrix scrollCellToVisibleAtRow: index column: 0];
    }
}

- (void) selectItemAtIndex: (int)index
{ 
  if (index < 0)
    return;
  
  if (!ForceBrowser)
    {
  if ([_tableView selectedRow] == index || [_tableView numberOfRows] <= index)
    return;
   _localSelection  = YES;
  // Will block the TableDidSelectionChange: action
  [_tableView selectRow: index byExtendingSelection: NO];     
  _localSelection = NO;
    }
  else
    {
  NSMatrix *matrix = [_browser matrixInColumn: 0];
  
  if ([matrix selectedRow] == index || [matrix numberOfRows] <= index)
    return;
  [_browser selectRow: index inColumn: 0];
    }   
}

- (void) deselectItemAtIndex: (int)index
{
  if (!ForceBrowser)
    {
  [_tableView deselectAll: self];
    }
  else
    {
  NSMatrix *matrix = [_browser matrixInColumn: 0];

  [matrix deselectSelectedCell];
    }
}

// Target/Action of Browser
- (void) selectItem: (id)sender
{
  if (_cell)
    {      
      [_cell selectItemAtIndex: [sender selectedRowInColumn: 0]];
      _stopped = YES;
    }
}

// Browser delegate methods
- (int) browser: (NSBrowser *)sender 
numberOfRowsInColumn: (int)column
{
  if (!_cell)
    return 0;

  return [_cell numberOfItems];
}

- (void) browser: (NSBrowser *)sender 
 willDisplayCell: (id)aCell
	   atRow: (int)row 
	  column: (int)column
{
  if (!_cell)
    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
{
  [self validateSelection];
}

// Key actions methods
- (void) moveUpSelection
{
  if (!ForceBrowser)
    {
  int index = [_tableView selectedRow] - 1;

  if (index > -1 && index < [_tableView numberOfRows])
    {
      _localSelection = YES;
      [_tableView selectRow: index byExtendingSelection: NO];
      [_tableView scrollRowToVisible: index];
      _localSelection = NO;
    }
    }
  else
    {
  int index = [_browser selectedRowInColumn: 0] - 1;

  if (index > -1 && index < [[_browser matrixInColumn: 0] numberOfRows])
    {
      _localSelection = YES;
      [_browser selectRow: index inColumn: 0];
      _localSelection = NO;
    }
    }
}

- (void) moveDownSelection
{
  if (!ForceBrowser)
    { 
  int index = [_tableView selectedRow] + 1;
  
  if (index > -1 && index < [_tableView numberOfRows])
    {
      _localSelection = YES;
      [_tableView selectRow: index byExtendingSelection: NO];
      [_tableView scrollRowToVisible: index];
      _localSelection = NO;
    }
    }
  else
    {
  int index = [_browser selectedRowInColumn: 0] + 1;

  if (index > -1 && index < [[_browser matrixInColumn: 0] numberOfRows])
    {
      _localSelection = YES;
      [_browser selectRow: index inColumn: 0];
      _localSelection = NO;
    }
    }
}

- (void) validateSelection
{
  if (_cell && _localSelection == NO)
    {
      if (!ForceBrowser)
        {
      [_cell selectItemAtIndex: [_tableView selectedRow]];
        }
      else
        {
      [_cell selectItemAtIndex: [_browser selectedRowInColumn: 0]];
	}
      _stopped = YES;
    }
}

@end

// ---

@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];
}

- (BOOL) hasVerticalScroller { return _hasVerticalScroller; }
- (void) setHasVerticalScroller: (BOOL)flag
{
  _hasVerticalScroller = flag;
}

- (NSSize) intercellSpacing { return _intercellSpacing; }
- (void) setIntercellSpacing: (NSSize)aSize
{
  _intercellSpacing = aSize;
}

- (float) itemHeight { return _itemHeight; }
- (void) setItemHeight: (float)itemHeight
{
  if (itemHeight > 14)
    _itemHeight = itemHeight;
}

- (int) numberOfVisibleItems { return _visibleItems; }
- (void) setNumberOfVisibleItems: (int)visibleItems
{
  if (visibleItems > 10)
    _visibleItems = visibleItems;
}

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

- (void) noteNumberOfItemsChanged
{
  [_popup noteNumberOfItemsChanged];
}

- (BOOL) usesDataSource { return _usesDataSource; }
- (void) setUsesDataSource: (BOOL)flag
{
  _usesDataSource = flag;
}

- (void) scrollItemAtIndexToTop: (int)index
{
  [_popup scrollItemAtIndexToTop: index];
}

- (void) scrollItemAtIndexToVisible: (int)index
{
  [_popup scrollItemAtIndexToVisible: index];
}

- (void) selectItemAtIndex: (int)index
{
  // Method called by GSComboWindow when a selection is done in the table view or 
  // the browser
  if (index < 0)
    return; // raise exception?
  if ([self numberOfItems] <= index)
    return; // raise exception?

  if (_selectedItem != index)
    {
      NSText *textObject = [[[self controlView] window] fieldEditor: YES 
							  forObject: self];  
      _selectedItem = 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
      
      [textObject setString: [self _stringValueAtIndex: _selectedItem]];
      [textObject setSelectedRange: NSMakeRange(0, [[textObject string] length])];

      [nc postNotificationName: NSComboBoxSelectionDidChangeNotification
	                object: [self controlView]
	              userInfo: nil];
    }
}

- (void) deselectItemAtIndex: (int)index
{
  if (_selectedItem == index)
    {
      _selectedItem = -1;

      [_popup deselectItemAtIndex: index];

      [nc postNotificationName: NSComboBoxSelectionDidChangeNotification
  	                object: [self controlView]
	              userInfo: nil];
    }
}

- (int) indexOfSelectedItem
{
  return _selectedItem;
}

- (int) numberOfItems
{
  if (_usesDataSource)
    {
      if (!_dataSource)
        {
	  NSLog(@"%@: A DataSource should be 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;
}

- (id) dataSource { return _dataSource; }
- (void) setDataSource: (id)aSource
{
  if (!_usesDataSource)
    NSLog(@"%@: This method is invalid, this combo box is not set to use a data source", 
      self);
  else
    _dataSource = aSource;
}

- (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];
}

- (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];
}

- (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];
}

- (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];
}

- (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];
}

- (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];
}

- (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];
   }
}

- (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];
    }
}

- (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];
    }
}

- (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];
}

- (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
- (NSString *)completedString:(NSString *)substring
{
  if (_usesDataSource)
    {
      if (!_dataSource)
	NSLog(@"%@: A data source should be 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;
}

- (BOOL)completes { return _completes; }
- (void)setCompletes:(BOOL)completes
{
  _completes = completes;
}

// Inlined methods
#define ButtonWidth 18
#define BorderWidth 2
// the inset border for the top and the bottom of the button

static inline NSRect textCellFrameFromRect(NSRect cellRect)
{
  return NSMakeRect(NSMinX(cellRect),
		    NSMinY(cellRect),
		    NSWidth(cellRect) - ButtonWidth,
		    NSHeight(cellRect));
}

static inline NSRect buttonCellFrameFromRect(NSRect cellRect)
{
  return NSMakeRect(NSMaxX(cellRect) - ButtonWidth,
		    NSMinY(cellRect) + BorderWidth,
		    ButtonWidth,
		    NSHeight(cellRect) - (BorderWidth * 2.0));
}

// 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]] */
}

- (void) drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
  // FIX ME: Is this test case below with the method call really needed ?
  if ([GSCurrentContext() isDrawingToScreen]) 
    {
      [super drawWithFrame: textCellFrameFromRect(cellFrame)
	            inView: controlView];
      [_buttonCell drawWithFrame: buttonCellFrameFromRect(cellFrame)
		          inView: controlView];
    }
  else
    {
      [super drawWithFrame: cellFrame inView: controlView];
    }
    
  _lastValidFrame = cellFrame; // used by GSComboWindow to appear in the right position
}

- (void) highlight: (BOOL)flag
	 withFrame: (NSRect)cellFrame
	    inView: (NSView *)controlView
{
  // FIX ME: Is this test case below with the method call really needed ?
  if ([GSCurrentContext() isDrawingToScreen])
    {
      [super highlight: flag
	     withFrame: textCellFrameFromRect(cellFrame)
	        inView: controlView];
      [_buttonCell highlight: flag
		   withFrame: buttonCellFrameFromRect(cellFrame)
		      inView: controlView];
    }
  else
    {
      [super highlight: flag withFrame: cellFrame inView: controlView];
    }
}

- (BOOL) trackMouse: (NSEvent *)theEvent 
	     inRect: (NSRect)cellFrame
	     ofView: (NSView *)controlView 
       untilMouseUp: (BOOL)flag
{
  NSWindow *cvWindow = [controlView window];
  NSPoint point;
  BOOL isFlipped = [controlView isFlipped];
  BOOL clicked = NO;
  
  // Should this be set by NSActionCell ?
  if (_control_view != controlView)
    _control_view = controlView;

  point = [controlView convertPoint: [theEvent locationInWindow]
			   fromView: nil];

  if (!NSMouseInRect(point, cellFrame, isFlipped))
    return NO; 
  else if ([theEvent type] == NSLeftMouseDown)
    {
      if (NSMouseInRect(point, textCellFrameFromRect(cellFrame), isFlipped))
	{
	  return YES;// continue
	}
      else if (NSMouseInRect(point, buttonCellFrameFromRect(cellFrame), isFlipped))
 	{
          [controlView lockFocus];
          [_buttonCell highlight: YES withFrame: buttonCellFrameFromRect(cellFrame) inView: controlView];
	  [controlView unlockFocus];
	  [cvWindow flushWindow];
	  
          clicked = [_buttonCell trackMouse: theEvent 
	                             inRect: buttonCellFrameFromRect(cellFrame)
	                             ofView: controlView
		  	       untilMouseUp: NO];

          /* We can do the call below but it is already done by the target/action we have set for the button cell
	  if (clicked)
	    [self _didClickWithinButton: self]; // not to be used */
	  	       
	  [controlView lockFocus];
          [_buttonCell highlight: NO withFrame: buttonCellFrameFromRect(cellFrame) inView: controlView];
	  [controlView unlockFocus];
	  [cvWindow flushWindow];
	  
	  return NO;
        }
    }
    
  return NO;
}

- (void) resetCursorRect: (NSRect)cellFrame inView: (NSView *)controlView
{
  [super resetCursorRect: textCellFrameFromRect(cellFrame)
	 inView: controlView];
}

- (void) setEnabled: (BOOL)flag
{
  [_buttonCell setEnabled: flag];
  [super setEnabled: flag];
}

// NSCoding
- (void) encodeWithCoder: (NSCoder *)coder
{
  [super encodeWithCoder: coder];

  [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];      
}

- (id) initWithCoder: (NSCoder *)aDecoder
{
  self = [super initWithCoder: aDecoder];
  
  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
{
  [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])
            {
	      [textObject setString: more];
	      location = myStringLength;
              length = [more length] - location;
	      [textObject setSelectedRange: NSMakeRange(location, length)];
	    }
        }
    }
}

@end

@implementation NSComboBoxCell (GNUstepPrivate)

- (NSString *) _stringValueAtIndex: (int)index
{
  if (!_usesDataSource)
    {
      return [[self itemObjectValueAtIndex: index] description];
    }
  else
    {
      if (!_dataSource)
        {
	  NSLog(@"%@: No data source currently specified", self);
	  return nil;
	}
      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];

  [controlView lockFocus];
  [_buttonCell highlight: YES 
	       withFrame: buttonCellFrameFromRect(cellFrame) 
	          inView: controlView];
  [controlView unlockFocus];
  [cvWindow flushWindow];

  [self _didClickWithinButton: self];
  
  [controlView lockFocus];
  [_buttonCell highlight: NO 
	       withFrame: buttonCellFrameFromRect(cellFrame) 
	          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)
        {
	  NSLog(@"%@: A DataSource should be 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];
    }
  else 
    {
      [self _setSelectedItem: -1];
    }
}

@end
