1/** <title>NSComboBoxCell</title>
2
3   Copyright (C) 1999 Free Software Foundation, Inc.
4
5   Author:  Gerrit van Dyk <gerritvd@decillion.net>
6   Date: 1999
7   Author:  Quentin Mathe <qmathe@club-internet.fr>
8   Date: 2004
9
10   This file is part of the GNUstep GUI Library.
11
12   This library is free software; you can redistribute it and/or
13   modify it under the terms of the GNU Lesser General Public
14   License as published by the Free Software Foundation; either
15   version 2 of the License, or (at your option) any later version.
16
17   This library is distributed in the hope that it will be useful,
18   but WITHOUT ANY WARRANTY; without even the implied warranty of
19   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
20   Lesser General Public License for more details.
21
22   You should have received a copy of the GNU Lesser General Public
23   License along with this library; see the file COPYING.LIB.
24   If not, see <http://www.gnu.org/licenses/> or write to the
25   Free Software Foundation, 51 Franklin Street, Fifth Floor,
26   Boston, MA 02110-1301, USA.
27*/
28
29#import <Foundation/NSNotification.h>
30#import <Foundation/NSString.h>
31#import <Foundation/NSArray.h>
32#import <Foundation/NSRunLoop.h>
33#import <Foundation/NSException.h>
34#import <Foundation/NSAutoreleasePool.h>
35#import <Foundation/NSValue.h>
36#import "AppKit/NSApplication.h"
37#import "AppKit/NSBox.h"
38#import "AppKit/NSBrowser.h"
39#import "AppKit/NSBrowserCell.h"
40#import "AppKit/NSButtonCell.h"
41#import "AppKit/NSComboBox.h"
42#import "AppKit/NSComboBoxCell.h"
43#import "AppKit/NSEvent.h"
44#import "AppKit/NSGraphicsContext.h"
45#import "AppKit/NSImage.h"
46#import "AppKit/NSPanel.h"
47#import "AppKit/NSScreen.h"
48#import "AppKit/NSScroller.h"
49#import "AppKit/NSScrollView.h"
50#import "AppKit/NSTableColumn.h"
51#import "AppKit/NSTableView.h"
52#import "AppKit/NSTextView.h"
53#import "GNUstepGUI/GSTheme.h"
54#import "GSGuiPrivate.h"
55
56static NSNotificationCenter *nc;
57
58@interface GSComboBoxTableView : NSTableView
59{
60}
61
62@end
63
64@implementation GSComboBoxTableView
65- (BOOL) acceptsFirstMouse: (NSEvent *)event
66{
67  return YES;
68}
69@end
70
71@interface GSComboWindow : NSPanel
72{
73   NSBrowser *_browser;
74   GSComboBoxTableView *_tableView;
75   NSComboBoxCell *_cell;
76   BOOL _stopped;
77}
78
79+ (GSComboWindow *) defaultPopUp;
80
81- (void) layoutWithComboBoxCell:(NSComboBoxCell *)comboBoxCell;
82- (void) positionWithComboBoxCell:(NSComboBoxCell *)comboBoxCell;
83- (void) popUpForComboBoxCell: (NSComboBoxCell *)comboBoxCell;
84- (void) runModalPopUpWithComboBoxCell:(NSComboBoxCell *)comboBoxCell;
85- (void) runLoopWithComboBoxCell:(NSComboBoxCell *)comboBoxCell;
86- (void) onWindowEdited: (NSNotification *)notification;
87- (void) clickItem: (id)sender;
88- (void) reloadData;
89- (void) noteNumberOfItemsChanged;
90- (void) scrollItemAtIndexToTop: (NSInteger)index;
91- (void) scrollItemAtIndexToVisible: (NSInteger)index;
92- (void) selectItemAtIndex: (NSInteger)index;
93- (void) deselectItemAtIndex: (NSInteger)index;
94- (void) moveUpSelection;
95- (void) moveDownSelection;
96- (void) validateSelection;
97
98@end
99
100@interface NSComboBoxCell (GNUstepPrivate)
101- (NSString *) _stringValueAtIndex: (NSInteger)index;
102- (void) _performClickWithFrame: (NSRect)cellFrame inView: (NSView *)controlView;
103- (void) _didClickWithinButton: (id)sender;
104- (BOOL) _isWantedEvent: (NSEvent *)event;
105- (GSComboWindow *) _popUp;
106- (NSRect) _textCellFrame;
107- (void) _setSelectedItem: (NSInteger)index;
108- (void) _loadButtonCell;
109- (void) _selectCompleted;
110@end
111
112// ---
113
114static GSComboWindow *gsWindow = nil;
115
116@implementation GSComboWindow
117
118+ (GSComboWindow *) defaultPopUp
119{
120  if (gsWindow == nil)
121    gsWindow = [[self alloc] initWithContentRect: NSMakeRect(0,0,200,200)
122			               styleMask: NSBorderlessWindowMask
123			                 backing: NSBackingStoreNonretained // NSBackingStoreBuffered
124			                   defer: YES];
125  return gsWindow;
126}
127
128- (id) initWithContentRect: (NSRect)contentRect
129		 styleMask: (NSUInteger)aStyle
130		   backing: (NSBackingStoreType)bufferingType
131		     defer: (BOOL)flag
132{
133  NSBox *box;
134  NSRect borderRect;
135  NSScrollView *scrollView;
136  NSTableColumn *column;
137  NSCell *cell;
138
139  self = [super initWithContentRect: contentRect
140		          styleMask: aStyle
141		            backing: bufferingType
142		              defer: flag];
143  if (nil == self)
144    return self;
145
146  [self setLevel: NSPopUpMenuWindowLevel];
147  [self setBecomesKeyOnlyIfNeeded: YES];
148
149  box = [[NSBox alloc] initWithFrame: contentRect];
150  [box setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
151  [box setBorderType: NSLineBorder];
152  [box setTitlePosition: NSNoTitle];
153  [box setContentViewMargins: NSMakeSize(0, 0)];
154  [self setContentView: box];
155  borderRect = contentRect;
156  RELEASE(box);
157
158  _tableView = [[GSComboBoxTableView alloc]
159                     initWithFrame: NSMakeRect(0, 0, 100, 100)];
160  [_tableView setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
161  //[_tableView setBackgroundColor: [NSColor whiteColor]];
162  [_tableView setDrawsGrid: NO];
163  [_tableView setAllowsEmptySelection: YES];
164  [_tableView setAllowsMultipleSelection: NO];
165  [_tableView setAutoresizesAllColumnsToFit: YES];
166  [_tableView setHeaderView: nil];
167  [_tableView setCornerView: nil];
168
169  column = [[NSTableColumn alloc] initWithIdentifier: @"content"];
170  cell = [[NSCell alloc] initTextCell: @""];
171  [column setDataCell: cell];
172  RELEASE(cell);
173  [_tableView addTableColumn: column];
174  RELEASE(column);
175
176  [_tableView setDataSource: self];
177  [_tableView setDelegate: self];
178  [_tableView setAction: @selector(clickItem:)];
179  [_tableView setTarget: self];
180
181  scrollView = [[NSScrollView alloc] initWithFrame: NSMakeRect(borderRect.origin.x,
182                                                               borderRect.origin.y,
183                                                               borderRect.size.width,
184                                                               borderRect.size.height)];
185  [scrollView setHasVerticalScroller: YES];
186  [scrollView setDocumentView: _tableView];
187  [_tableView release];
188  [box setContentView: scrollView];
189  RELEASE(scrollView);
190
191  [_tableView reloadData];
192
193  return self;
194}
195
196- (BOOL) canBecomeKeyWindow
197{
198  return YES;
199}
200
201- (void)dealloc
202{
203  // Browser, table view and scroll view were not retained so don't release them
204  [super dealloc];
205}
206
207- (void) layoutWithComboBoxCell: (NSComboBoxCell *)comboBoxCell
208{
209  NSSize bsize = [[GSTheme theme] sizeForBorderType: NSLineBorder];
210  NSSize size;
211  CGFloat itemHeight;
212  CGFloat textCellWidth;
213  CGFloat popUpWidth;
214  NSSize intercellSpacing;
215  NSInteger num = [comboBoxCell numberOfItems];
216  NSInteger max = [comboBoxCell numberOfVisibleItems];
217
218  // Manage table view or browser cells height
219
220  itemHeight = [comboBoxCell itemHeight];
221  if (itemHeight <= 0)
222    {
223      // FIX ME : raise NSException
224      itemHeight = [_tableView rowHeight];
225    }
226  size.height = itemHeight;
227
228  // Manage table view or browser cells width
229
230  textCellWidth = [comboBoxCell _textCellFrame].size.width;
231  if ([comboBoxCell hasVerticalScroller])
232    {
233      popUpWidth = textCellWidth + [NSScroller scrollerWidth];
234    }
235  else
236    {
237      popUpWidth = textCellWidth;
238    }
239  size.width = textCellWidth - bsize.width;
240
241  if (size.width < 0)
242    {
243      size.width = 0;
244    }
245
246  [_tableView setRowHeight: size.height];
247
248  // Just check intercell spacing
249
250  intercellSpacing = [comboBoxCell intercellSpacing];
251  if (intercellSpacing.height <= 0)
252    {
253      // FIX ME : raise NSException
254      intercellSpacing.height = [_tableView intercellSpacing].height;
255    }
256  else
257    {
258      [_tableView setIntercellSpacing: intercellSpacing];
259    }
260
261
262  if (num > max)
263    num = max;
264
265  [self setFrame: [self frameRectForContentRect: NSMakeRect(0, 0, popUpWidth,
266     2 * bsize.height + (itemHeight + intercellSpacing.height) * (num - 1)
267     + itemHeight)] display: NO];
268}
269
270- (void) positionWithComboBoxCell: (NSComboBoxCell *)comboBoxCell
271{
272  NSView *viewWithComboCell = [comboBoxCell controlView];
273  NSRect screenFrame;
274  NSRect comboWindowFrame;
275  NSRect viewWithComboCellFrame;
276  NSRect rect;
277  NSPoint point, oldPoint;
278
279  [self layoutWithComboBoxCell: comboBoxCell];
280
281  // Now we can ask for the size
282  comboWindowFrame = [self frame];
283  if (comboWindowFrame.size.width == 0 || comboWindowFrame.size.height == 0)
284    return;
285
286  screenFrame = [[[viewWithComboCell window] screen] frame];
287  viewWithComboCellFrame = [comboBoxCell _textCellFrame];
288  if ([viewWithComboCell isFlipped])
289  {
290      point = viewWithComboCellFrame.origin;
291      point.y = NSMaxY(viewWithComboCellFrame);
292  }
293  else
294  {
295      point = viewWithComboCellFrame.origin;
296  }
297
298  // Switch to the window coordinates
299  point = [viewWithComboCell convertPoint: point toView: nil];
300
301  // Switch to the screen coordinates
302  point = [[viewWithComboCell window] convertBaseToScreen: point];
303  point.y -= 1 + NSHeight(comboWindowFrame);
304
305  if (point.y < 0)
306    {
307      // Off screen, so move it
308      oldPoint = point;
309
310      point = viewWithComboCellFrame.origin;
311      point.y = NSMaxY(viewWithComboCellFrame);
312
313      // Switch to the window coordinates
314      point = [viewWithComboCell convertPoint: point toView: nil];
315
316      // Switch to the screen coordiantes
317      point = [[viewWithComboCell window] convertBaseToScreen: point];
318      point.y += 1;
319
320      if (point.y + NSHeight(comboWindowFrame) > NSHeight(screenFrame))
321	  point = oldPoint;
322    }
323
324  rect.size.width = NSWidth(comboWindowFrame);
325  rect.size.height = NSHeight(comboWindowFrame);
326  rect.origin.x = point.x;
327  rect.origin.y = point.y;
328  [self setFrame: rect display: NO];
329}
330
331- (void) popUpForComboBoxCell: (NSComboBoxCell *)comboBoxCell
332{
333  _cell = comboBoxCell;
334
335  [self positionWithComboBoxCell: _cell];
336  [_cell _selectCompleted];
337  [self reloadData];
338  [self enableKeyEquivalentForDefaultButtonCell];
339  [self runModalPopUpWithComboBoxCell: _cell];
340
341  _cell = nil;
342  [self deselectItemAtIndex: 0];
343}
344
345- (void) runModalPopUpWithComboBoxCell: (NSComboBoxCell *)comboBoxCell
346{
347  NSWindow	*onWindow;
348
349  onWindow = [[_cell controlView] window];
350
351  [nc addObserver: self selector: @selector(onWindowEdited:)
352    name: NSWindowWillCloseNotification object: onWindow];
353  [nc addObserver: self selector: @selector(onWindowEdited:)
354    name: NSWindowWillMoveNotification object: onWindow];
355  [nc addObserver: self selector: @selector(onWindowEdited:)
356    name: NSWindowWillMiniaturizeNotification object: onWindow];
357
358  // FIX ME: The notification below doesn't exist currently
359  // [nc addObserver: self selector: @selector(onWindowEdited:)
360  //   name: NSWindowWillResizeNotification object: onWindow];
361
362
363  // FIXME: The code below must be removed when the notifications over will work
364  [nc addObserver: self selector: @selector(onWindowEdited:)
365    name: NSWindowDidMoveNotification object: onWindow];
366  [nc addObserver: self selector: @selector(onWindowEdited:)
367    name: NSWindowDidMiniaturizeNotification object: onWindow];
368  [nc addObserver: self selector: @selector(onWindowEdited:)
369    name: NSWindowDidResizeNotification object: onWindow];
370  // End of the code to remove
371
372  [self orderFront: self];
373  [self makeFirstResponder: _tableView];
374  [self runLoopWithComboBoxCell: comboBoxCell];
375
376  [nc removeObserver: self name: nil object: onWindow];
377
378  [self close];
379
380  [onWindow makeFirstResponder: [_cell controlView]];
381}
382
383- (void) runLoopWithComboBoxCell: (NSComboBoxCell *)comboBoxCell
384{
385  NSEvent *event;
386  NSDate *limit = [NSDate distantFuture];
387  unichar key;
388  CREATE_AUTORELEASE_POOL (pool);
389
390  while (YES)
391    {
392      event = [NSApp nextEventMatchingMask: NSAnyEventMask
393		                 untilDate: limit
394		                    inMode: NSDefaultRunLoopMode
395		                   dequeue: YES];
396      if ([event type] == NSLeftMouseDown
397       || [event type] == NSRightMouseDown)
398        {
399          if ([comboBoxCell _isWantedEvent: event] == NO && [event window] != self)
400	    {
401              break;
402	    }
403	  else
404	    {
405	      [NSApp sendEvent: event];
406	    }
407        }
408      else if ([event type] == NSKeyDown)
409        {
410	  key = [[event characters] characterAtIndex: 0];
411          if (key == NSUpArrowFunctionKey)
412            {
413              [self moveUpSelection];
414            }
415          else if (key == NSDownArrowFunctionKey)
416            {
417              [self moveDownSelection];
418            }
419          else if (key == NSNewlineCharacter
420	    || key == NSEnterCharacter
421	    || key == NSCarriageReturnCharacter)
422            {
423              [self validateSelection];
424            }
425	  else if (key == 0x001b)
426	    {
427	      break;
428	    }
429          else
430            {
431	      [NSApp sendEvent: event];
432	    }
433	}
434      else
435        {
436	  [NSApp sendEvent: event];
437	}
438
439      if (_stopped)
440        break;
441    }
442
443  _stopped = NO;
444
445  [pool drain];
446}
447
448// onWindow notifications
449
450- (void) onWindowEdited: (NSNotification *)notification
451{
452  _stopped = YES;
453}
454
455- (void) reloadData
456{
457  [_tableView reloadData];
458  [self selectItemAtIndex: [_cell indexOfSelectedItem]];
459}
460
461- (void) noteNumberOfItemsChanged
462{
463  // FIXME: Probably should only load the additional items
464  [self reloadData];
465}
466
467- (void) scrollItemAtIndexToTop: (NSInteger)index
468{
469  NSRect rect;
470
471  rect = [_tableView frameOfCellAtColumn: 0 row: index];
472  [_tableView scrollPoint: rect.origin];
473}
474
475- (void) scrollItemAtIndexToVisible: (NSInteger)index
476{
477  [_tableView scrollRowToVisible: index];
478}
479
480- (void) selectItemAtIndex: (NSInteger)index
481{
482  if (index < 0)
483    return;
484
485  if ([_tableView selectedRow] == index || [_tableView numberOfRows] <= index)
486    return;
487
488  [_tableView selectRow: index byExtendingSelection: NO];
489}
490
491- (void) deselectItemAtIndex: (NSInteger)index
492{
493  [_tableView deselectAll: self];
494}
495
496// Target/Action method
497- (void) clickItem: (id)sender
498{
499  if (_cell == nil)
500    return;
501
502  [_cell _setSelectedItem: [sender selectedRow]];
503  [self validateSelection];
504
505  [nc postNotificationName: NSComboBoxSelectionDidChangeNotification
506	            object: [_cell controlView]
507	          userInfo: nil];
508}
509
510// Browser delegate methods
511- (NSInteger) browser: (NSBrowser *)sender numberOfRowsInColumn: (NSInteger)column
512{
513  if (_cell == nil)
514    return 0;
515
516  return [_cell numberOfItems];
517}
518
519- (void) browser: (NSBrowser *)sender
520 willDisplayCell: (id)aCell
521	   atRow: (NSInteger)row
522	  column: (NSInteger)column
523{
524  if (_cell == nil)
525    return;
526
527  [aCell setStringValue: [_cell _stringValueAtIndex: row]];
528  [aCell setLeaf: YES];
529}
530
531// Table view data source methods
532- (NSInteger) numberOfRowsInTableView: (NSTableView *)tv
533{
534  return [_cell numberOfItems];
535}
536
537- (id) tableView: (NSTableView *)tv objectValueForTableColumn: (NSTableColumn *)tc row: (NSInteger)row
538{
539  return [_cell _stringValueAtIndex: row];
540}
541
542// Table view delegate methods
543- (void) tableViewSelectionDidChange: (NSNotification *)notification
544{
545  [_cell _setSelectedItem: [[notification object] selectedRow]];
546
547  [nc postNotificationName: NSComboBoxSelectionDidChangeNotification
548	            object: [_cell controlView]
549	          userInfo: nil];
550}
551
552// Key actions methods
553- (void) moveUpSelection
554{
555  NSInteger index = [_tableView selectedRow] - 1;
556
557  if (index > -1 && index < [_tableView numberOfRows])
558    {
559      [_tableView selectRow: index byExtendingSelection: NO];
560      [_tableView scrollRowToVisible: index];
561    }
562}
563
564- (void) moveDownSelection
565{
566  NSInteger index = [_tableView selectedRow] + 1;
567
568  if (index > -1 && index < [_tableView numberOfRows])
569    {
570      [_tableView selectRow: index byExtendingSelection: NO];
571      [_tableView scrollRowToVisible: index];
572    }
573}
574
575- (void) validateSelection
576{
577  if (_cell != nil)
578    {
579      NSText	*textObject = nil;
580      id	cv = [_cell controlView];
581      NSInteger	index = [_cell indexOfSelectedItem];
582
583      if ([cv isKindOfClass: [NSControl class]])
584        {
585	  textObject = [(NSControl *)cv currentEditor];
586	}
587
588      if (index != -1)
589        {
590          [_cell setStringValue: [_cell _stringValueAtIndex:
591            [_cell indexOfSelectedItem]]];
592          // Will update the editor when needed
593        }
594
595      if  (textObject != nil)
596        {
597	  NSRange selectionRange = NSMakeRange(0, [[textObject string] length]);
598	  [textObject setSelectedRange: selectionRange];
599	  [textObject scrollRangeToVisible: selectionRange];
600	}
601
602      [cv sendAction: [_cell action] to: [_cell target]];
603
604      _stopped = YES;
605    }
606}
607
608@end
609
610// ---
611
612/**
613 <unit>
614 <heading>Class Description</heading>
615 <p>An NSComboBoxCell is what we can call a completion/choices box cell, derived from
616 NSTextFieldCell, it allows you to enter text like in a text field but also to click
617 in the ellipsis button (indicating the fact other user inputs are possible) on
618 the right of it to obtain a list of choices, you can use them as the text field
619 value by selecting a row in this list. You can also obtain direct completion
620 when it  is enabled via <code>setCompletes:</code> to get a suggested text
621 field value updated as you type. </p>
622 <p>Like other NSCell classes, NSComboBoxCell has a matching NSControl named NSComboBox
623 which is relying on it to implement the combo box behavior in a standalone
624 control.</p>
625 </unit>
626*/
627
628/**
629 * <p>No special instructions to use NSComboBoxCell or text to detail the implementation.</p>
630 */
631@implementation NSComboBoxCell
632
633/*
634 * Class methods
635 */
636+ (void) initialize
637{
638  if (self == [NSComboBoxCell class])
639    {
640      [NSComboBoxCell setVersion: 2];
641      nc = [NSNotificationCenter defaultCenter];
642    }
643}
644
645- (id) initTextCell: (NSString *)aString
646{
647  self = [super initTextCell: aString];
648
649  // Implicitly set by allocation:
650  //
651  //_dataSource = nil;
652  //_buttonCell = nil;
653  //_usesDataSource = NO;
654  //_completes = NO;
655  _popUpList = [[NSMutableArray alloc] init];
656  _hasVerticalScroller = YES;
657  _visibleItems = 10;
658  _intercellSpacing = NSMakeSize(3.0, 2.0);
659  _itemHeight = 16;
660  _selectedItem = -1;
661
662  [self _loadButtonCell];
663
664  return self;
665}
666
667- (void) dealloc
668{
669  RELEASE(_buttonCell);
670  RELEASE(_popUpList);
671
672  [super dealloc];
673}
674
675- (id) copyWithZone: (NSZone*)zone
676{
677  NSComboBoxCell *c = [super copyWithZone: zone];
678
679  c->_buttonCell = [_buttonCell copyWithZone: zone];
680  [c->_buttonCell setTarget: c];
681  c->_popUpList = [_popUpList copyWithZone: zone];
682
683  return c;
684}
685
686/**
687 * Returns YES when the combo box cell displays a vertical scroller for its
688 * list, returns NO otherwise.
689 * Take note that the scroller will be displayed even when the sum of the items
690 * height in the list is inferior to the minimal height of the list displayed
691 * area.
692 */
693- (BOOL) hasVerticalScroller
694{
695  return _hasVerticalScroller;
696}
697
698/**
699 * Sets whether the combo box cell list displays a vertical scroller, by default
700 * it is the case. When <var>flag</var> is NO and the combo cell list has more
701 * items (either in its default list or from its data source) than the number
702 * returned by <code>numberOfVisibleItems</code>, only a subset of them will be
703 * displayed. Uses scroll related methods to position this subset in the combo
704 * box cell list.
705 * Take note that the scroller will be displayed even when the sum of the items
706 * height in the list is inferior to the minimal height of the list displayed
707 * area.
708 */
709- (void) setHasVerticalScroller: (BOOL)flag
710{
711  _hasVerticalScroller = flag;
712}
713
714/**
715 * Returns the width and the height (as the values of an NSSize variable)
716 * between each item of the combo box cell list.
717 */
718- (NSSize) intercellSpacing
719{
720  return _intercellSpacing;
721}
722
723/**
724 * Sets the width and the height between each item of the combo box cell list to
725 * the values in <var>aSize</var>.
726 */
727- (void) setIntercellSpacing: (NSSize)aSize
728{
729  _intercellSpacing = aSize;
730}
731
732/**
733 * Returns the height of the items in the combo box cell list.
734 */
735- (CGFloat) itemHeight
736{
737  return _itemHeight;
738}
739
740/**
741 * Sets the height of the items in the combo box cell list to
742 * <var>itemHeight</var>.
743 */
744- (void) setItemHeight: (CGFloat)itemHeight
745{
746  if (itemHeight > 14.0)
747    _itemHeight = itemHeight;
748}
749
750/**
751 * Returns the maximum number of allowed items to be displayed in the combo box
752 * cell list.
753 */
754- (NSInteger) numberOfVisibleItems
755{
756  return _visibleItems;
757}
758
759/**
760 * Sets the maximum number of allowed items to be displayed in the combo box
761 * cell list.
762 */
763- (void) setNumberOfVisibleItems: (NSInteger)visibleItems
764{
765  if (visibleItems > 10)
766    _visibleItems = visibleItems;
767}
768
769/**
770 * Marks the combo box cell in order to have its items list reloaded in the
771 * case it uses a data source, and to have it redisplayed.
772 */
773- (void) reloadData
774{
775  [_popup reloadData];
776}
777
778/**
779 * Informs the combo box cell that the number of items in its data source has
780 * changed, in order to permit to the scrollers in its displayed list being
781 * updated without needing the reload of the data.
782 * It is recommended to use this method with a data source that continually
783 * receives data in the background, to keep the the combo box cell responsive to
784 * the user while the data is received.
785 * Take a look at the <code>NSComboBoxDataSource</code> informal protocol
786 * specification to know more on the messages NSComboBox sends to its data
787 * source.
788 */
789- (void) noteNumberOfItemsChanged
790{
791  [_popup noteNumberOfItemsChanged];
792}
793
794/**
795 * Returns YES when the combo box cell uses a data source (which is external) to
796 * populate its items list, otherwise returns NO in the case it uses its default
797 * list.
798 */
799- (BOOL) usesDataSource
800{
801  return _usesDataSource;
802}
803
804/**
805 * Sets according to <var>flag</var> whether the combo box cell uses a data
806 * source (which is external) to populate its items list.
807 */
808- (void) setUsesDataSource: (BOOL)flag
809{
810  _usesDataSource = flag;
811}
812
813/**
814 * Scrolls the combo box cell list vertically in order to have the item at
815 * <var>index</var> in the closest position relative to the top. There is no
816 * need to have the list displayed when this method is invoked.
817 */
818- (void) scrollItemAtIndexToTop: (NSInteger)index
819{
820  [_popup scrollItemAtIndexToTop: index];
821}
822
823/**
824 * Scrolls the combo box cell list vertically in order to have the item at
825 * <var>index</var> visible. There is no need to have the list displayed when
826 * this method is invoked.
827 */
828- (void) scrollItemAtIndexToVisible: (NSInteger)index
829{
830  [_popup scrollItemAtIndexToVisible: index];
831}
832
833/**
834 * Selects the combo box cell list row at <var>index</var>.
835 * Take note no changes occurs in the combo box cell list when this method is
836 * called.
837 * Posts an NSComboBoxSelectionDidChangeNotification to the default notification
838 * center when there is a new selection different from the previous one.
839 */
840- (void) selectItemAtIndex: (NSInteger)index
841{
842  // Method called by GSComboWindow when a selection is done in the table view or
843  // the browser
844
845  if (index < 0 || [self numberOfItems] <= index)
846    return; // FIXME: Probably we should raise an exception
847
848  if (_selectedItem != index)
849    {
850      [self _setSelectedItem: index];
851
852      [_popup selectItemAtIndex: index];
853      // This method call will not create a infinite loop when the index has been
854      // already set by a mouse click because the method is not completed when the
855      // current index is not different from the index parameter
856
857      [nc postNotificationName: NSComboBoxSelectionDidChangeNotification
858	                object: [self controlView]
859	              userInfo: nil];
860    }
861}
862
863/**
864 * Deselects the combo box cell list row at <var>index</var> in the case this
865 * row is selected.
866 * Posts an NSComboBoxSelectionDidChangeNotification to the default notification
867 * center, when there is a new selection.
868 */
869- (void) deselectItemAtIndex: (NSInteger)index
870{
871  if (_selectedItem == index)
872    {
873      [self _setSelectedItem: -1];
874
875      [_popup deselectItemAtIndex: index];
876
877      [nc postNotificationName: NSComboBoxSelectionDidChangeNotification
878  	                object: [self controlView]
879	              userInfo: nil];
880    }
881}
882
883/**
884 * Returns the index of the selected item in the combo box cell list or -1 when
885 * there is no selection, the selected item can be related to the data source
886 * object in the case <code>usesDataSource</code> returns YES else to the
887 * default items list.
888 */
889- (NSInteger) indexOfSelectedItem
890{
891  return _selectedItem;
892}
893
894/**
895 * Returns the number of items in the the combo box cell list, the numbers of
896 * items can be be related to the data source object in the case
897 * <code>usesDataSource</code> returns YES else to the default items list.
898 */
899- (NSInteger) numberOfItems
900{
901  if (_usesDataSource)
902    {
903      if (_dataSource == nil)
904        {
905          NSLog(@"%@: No data source currently specified", self);
906        }
907      else if ([_dataSource respondsToSelector:
908                 @selector(numberOfItemsInComboBox:)])
909        {
910          return [_dataSource numberOfItemsInComboBox:
911            (NSComboBox *)[self controlView]];
912        }
913      else if ([_dataSource respondsToSelector:
914                  @selector(numberOfItemsInComboBoxCell:)])
915        {
916          return [_dataSource numberOfItemsInComboBoxCell: self];
917        }
918    }
919  else
920    {
921      return [_popUpList count];
922    }
923
924  return 0;
925}
926
927/**
928 * Returns the combo box cell data source object which is reponsible to provide
929 * the data to be displayed. To know how to implement a data source object,
930 * take a  look at the NSComboBoxDataSource informal protocol description. In
931 * the case <code>usesDataSource</code> returns NO, this method logs a warning.
932 */
933- (id) dataSource
934{
935  return _dataSource;
936}
937
938/**
939 * Sets the combo box cell data source to <var>aSource</var>. Just calling this
940 * method doesn't set <code>usesDataSource</code> to return YES, you must call
941 * <code>setUsesDataSource:</code> with YES before or a warning will be logged.
942 * To know how to implement a data source objects, take a  look at the
943 * NSComboBoxDataSource informal protocol description. When <var>aSource</var>
944 * doesn't respond to the methods <code>numberOfItemsInComboBox:</code>
945 * <code>comboBox:objectValueForItemAtIndex:</code>, this method
946 * logs a warning.
947 */
948- (void) setDataSource: (id)aSource
949{
950  if (_usesDataSource == NO)
951    {
952      NSLog(@"%@: This method is invalid, this combo box is not set to use a data source",
953        self);
954    }
955  else
956    {
957      _dataSource = aSource;
958    }
959}
960
961/**
962 * Adds an item to the combo box cell default items list which is used when
963 * <code>usesDataSource</code> returns NO. In the case
964 * <code>usesDataSource</code> returns YES, this method logs a warning.
965 */
966- (void) addItemWithObjectValue: (id)object
967{
968  if (_usesDataSource)
969    {
970      NSLog(@"%@: This method is invalid, this combo box is set to use a data source",
971        self);
972    }
973  else
974    {
975      [_popUpList addObject: object];
976    }
977
978  [self reloadData];
979}
980
981/**
982 * Adds several items in an array to the combo box cell default items list which
983 * is used when <code>usesDataSource</code> returns NO. In the case
984 * <code>usesDataSource</code> returns YES, this method logs a warning.
985 */
986- (void) addItemsWithObjectValues: (NSArray *)objects
987{
988  if (_usesDataSource)
989    {
990      NSLog(@"%@: This method is invalid, this combo box is set to use a data source",
991        self);
992    }
993  else
994    {
995      [_popUpList addObjectsFromArray: objects];
996    }
997
998  [self reloadData];
999}
1000
1001/**
1002 * Inserts an item in the combo box cell default items list which
1003 * is used when <code>usesDataSource</code> returns NO. In the case
1004 * <code>usesDataSource</code> returns YES, this method logs a warning.
1005 */
1006- (void) insertItemWithObjectValue: (id)object atIndex: (NSInteger)index
1007{
1008  if (_usesDataSource)
1009    {
1010      NSLog(@"%@: This method is invalid, this combo box is set to use a data source",
1011        self);
1012    }
1013  else
1014    {
1015      [_popUpList insertObject: object atIndex: index];
1016    }
1017
1018  [self reloadData];
1019}
1020
1021/**
1022 * Removes an item in the combo box cell default items list which
1023 * is used when <code>usesDataSource</code> returns NO. In the case
1024 * <code>usesDataSource</code> returns YES, this method logs a warning.
1025 */
1026- (void) removeItemWithObjectValue: (id)object
1027{
1028  if (_usesDataSource)
1029    {
1030      NSLog(@"%@: This method is invalid, this combo box is set to use a data source",
1031        self);
1032    }
1033  else
1034    {
1035      [_popUpList removeObject: object];
1036    }
1037
1038  [self reloadData];
1039}
1040
1041/**
1042 * Removes the item with the specified <var>index</var> in the combo box cell
1043 * default items list which is used when <code>usesDataSource</code> returns NO.
1044 * In the case <code>usesDataSource</code> returns YES, this method logs a warning.
1045 */
1046- (void) removeItemAtIndex: (NSInteger)index
1047{
1048  if (_usesDataSource)
1049    {
1050      NSLog(@"%@: This method is invalid, this combo box is set to use a data source",
1051        self);
1052    }
1053  else
1054    {
1055      [_popUpList removeObjectAtIndex: index];
1056    }
1057
1058  [self reloadData];
1059}
1060
1061/**
1062 * Removes all the items in the combo box cell default items list which is used
1063 * when <code>usesDataSource</code> returns NO. In the case
1064 * <code>usesDataSource</code> returns YES, this method logs a warning.
1065 */
1066- (void) removeAllItems
1067{
1068  if (_usesDataSource)
1069    {
1070      NSLog(@"%@: This method is invalid, this combo box is set to use a data source",
1071        self);
1072    }
1073  else
1074    {
1075      [_popUpList removeAllObjects];
1076    }
1077
1078  [self reloadData];
1079}
1080
1081/**
1082 * Selects the first item in the default combo box cell list which is equal to
1083 * <var>object</var>. In the case <code>usesDataSource</code> returns YES, this
1084 * method logs a warning.
1085 * Take note that this method doesn't update the text field part value.
1086 * Posts an NSComboBoxSelectionDidChange notification to the default
1087 * notification center when the new selection is different than the previous
1088 * one.
1089 */
1090- (void) selectItemWithObjectValue: (id)object
1091{
1092 if (_usesDataSource)
1093   {
1094     NSLog(@"%@: This method is invalid, this combo box is set to use a data source",
1095       self);
1096   }
1097 else
1098   {
1099     NSInteger i = [_popUpList indexOfObject: object];
1100
1101     if (i == NSNotFound)
1102       i = -1;
1103
1104     [self selectItemAtIndex: i];
1105   }
1106}
1107
1108/**
1109 * Returns the object value at <var>index</var> within combo box cell default
1110 * items list. When the index is beyond the end of the list, an NSRangeException is
1111 * raised. In the case <code>usesDataSource</code> returns YES, this method logs
1112 * a warning.
1113 */
1114- (id) itemObjectValueAtIndex: (NSInteger)index
1115{
1116  if (_usesDataSource)
1117    {
1118      NSLog(@"%@: This method is invalid, this combo box is set to use a data source",
1119        self);
1120      return nil;
1121    }
1122  else
1123    {
1124      return [_popUpList objectAtIndex: index];
1125    }
1126}
1127
1128/* FIXME: Not sure, if this is the best way to implement objectValue,
1129 * perhaps it would be better to store the current value with setObjectValue:
1130 * whenever it changes.
1131 */
1132- (id) objectValue
1133{
1134  NSInteger index = [self indexOfSelectedItem];
1135
1136  if (index == -1)
1137    {
1138      return nil;
1139    }
1140  else
1141    {
1142      if (_usesDataSource)
1143        {
1144	  if (_dataSource == nil)
1145	    {
1146	      NSLog(@"%@: No data source currently specified", self);
1147	      return nil;
1148	    }
1149	  if ([_dataSource respondsToSelector:
1150			       @selector(comboBox:objectValueForItemAtIndex:)])
1151	    {
1152	      return [_dataSource comboBox: (NSComboBox *)[self controlView]
1153				  objectValueForItemAtIndex: index];
1154	    }
1155	  else if ([_dataSource respondsToSelector:
1156				    @selector(comboBoxCell:objectValueForItemAtIndex:)])
1157	    {
1158	      return [_dataSource comboBoxCell: self
1159				  objectValueForItemAtIndex: index];
1160	    }
1161	}
1162      else
1163        {
1164	  return [self itemObjectValueAtIndex: index];
1165	}
1166    }
1167
1168  return nil;
1169}
1170
1171/**
1172 * Returns the object value of the selected item in the combo box cell default
1173 * items list or nil when there is no selection. In the case
1174 * <code>usesDataSource</code> returns YES, this method logs a warning.
1175 */
1176- (id) objectValueOfSelectedItem
1177{
1178  if (_usesDataSource)
1179    {
1180      NSLog(@"%@: This method is invalid, this combo box is set to use a data source",
1181        self);
1182      return nil;
1183    }
1184  else
1185    {
1186      NSInteger index = [self indexOfSelectedItem];
1187
1188      if (index == -1)
1189        {
1190          return nil;
1191        }
1192      else
1193        {
1194          return [_popUpList objectAtIndex: index];
1195        }
1196    }
1197}
1198
1199/**
1200 * Returns the lowest index associated with a value in the combo box
1201 * cell default items list, which is equal to <var>object</var>, and returns
1202 * NSNotFound when there is no such value. In the case
1203 * <code>usesDataSource</code> returns YES, this method logs a warning.
1204 */
1205- (NSInteger) indexOfItemWithObjectValue: (id)object
1206{
1207  if (_usesDataSource)
1208    {
1209      NSLog(@"%@: This method is invalid, this combo box is set to use a data source",
1210        self);
1211      return 0;
1212    }
1213
1214  return [_popUpList indexOfObject: object];
1215}
1216
1217/**
1218 * Returns the combo box cell default items list in an array.
1219 */
1220- (NSArray *) objectValues
1221{
1222  if (_usesDataSource)
1223    {
1224      NSLog(@"%@: This method is invalid, this combo box is set to use a data source",
1225        self);
1226      return nil;
1227    }
1228
1229  return _popUpList;
1230}
1231
1232// Text completion
1233/**
1234 * Returns a string by looking in the combo box cell list for an item wich
1235 * starts with <var>substring</var>, or nil when there is no such string.
1236 * <var>substring</var> is equal to what the user entered in the text field
1237 * part.
1238 * You rarely needs to call this method explicitly in your code.
1239 * By default, the implementation of this method first checks whether the combo
1240 * box cell uses a data source and whether the data source responds to
1241 * <code>comboBox:completedString:</code> or <code>comboBoxCell:completedString:</code>.
1242 * When it is the case, it uses this method to return <var>str</var>, else this
1243 * method goes through the combo box cell items one by one and returns the first
1244 * item found starting with <var>substring</var>.
1245 * In the case, you want another behavior, you can override this method without
1246 * need to call the superclass method.
1247 */
1248- (NSString *) completedString: (NSString *)substring
1249{
1250  if (nil == substring)
1251    {
1252      return nil;
1253    }
1254
1255  if (_usesDataSource)
1256    {
1257      if (_dataSource == nil)
1258        {
1259	  NSLog(@"%@: No data source currently specified", self);
1260	}
1261      else if ([_dataSource respondsToSelector: @selector(comboBox:completedString:)])
1262        {
1263	  return [_dataSource comboBox: (NSComboBox *)[self controlView]
1264		       completedString: substring];
1265	}
1266      else if ([_dataSource respondsToSelector: @selector(comboBoxCell:completedString:)])
1267        {
1268	  return [_dataSource comboBoxCell: self completedString: substring];
1269	}
1270      else
1271        {
1272          NSInteger i;
1273
1274          for (i = 0; i < [self numberOfItems]; i++)
1275            {
1276	      NSString *str = [self _stringValueAtIndex: i];
1277
1278	      if ([str length] > [substring length] && [str hasPrefix: substring])
1279	        return str;
1280            }
1281	}
1282    }
1283  else
1284    {
1285      NSUInteger i;
1286
1287      for (i = 0; i < [_popUpList count]; i++)
1288        {
1289	  NSString *str = [[_popUpList objectAtIndex: i] description];
1290
1291	  if ([str length] > [substring length] && [str hasPrefix: substring])
1292	    return str;
1293        }
1294    }
1295
1296  return substring;
1297}
1298
1299/**
1300 * Returns YES when the combo box cell automatic completion is active, returns
1301 * NO otherwise.
1302 * Take a look at the <code>setCompletes:</code> method documentation to know
1303 * how the automatic completion works.
1304 */
1305- (BOOL) completes
1306{
1307  return _completes;
1308}
1309
1310/**
1311 * Sets whether the combo box cell automatic completion is active or not.
1312 * The automatic completion tries to complete what the user types in the text
1313 * field part, it tries to complete only when the the user adds characters at
1314 * the end of the string, not when it deletes characters or when the insertion
1315 * point precedes the end of the string.
1316 * To do the automatic completion, the <code>completedString:</code> method is
1317 * called, and when the returned string is longer than the current one in the text
1318 * field, the completion occurs and the completed part gets selected.
1319 */
1320- (void) setCompletes: (BOOL)completes
1321{
1322  _completes = completes;
1323}
1324
1325- (BOOL) isButtonBordered
1326{
1327  return [_buttonCell isBordered];
1328}
1329
1330- (void) setButtonBordered:(BOOL)flag
1331{
1332  [_buttonCell setBordered: flag];
1333}
1334
1335#define ComboBoxHeight 21 // FIX ME: All this stuff shouldn't be hardcoded
1336#define ButtonWidth 17
1337#define ButtonHeight 17
1338#define BorderSize 2
1339// The inset border for the top and the bottom of the button
1340
1341/*
1342 * Inlined methods
1343 */
1344
1345static inline NSRect textCellFrameFromRect(NSRect cellRect)
1346// Not the drawed part, precises just the part which receives events
1347{
1348  return NSMakeRect(NSMinX(cellRect),
1349		    NSMinY(cellRect),
1350		    NSWidth(cellRect) - ButtonWidth - BorderSize,
1351		    NSHeight(cellRect));
1352}
1353
1354static inline NSRect buttonCellFrameFromRect(NSRect cellRect)
1355{
1356  return NSMakeRect(NSMaxX(cellRect) - ButtonWidth - BorderSize,
1357		    NSMinY(cellRect) + BorderSize,
1358		    ButtonWidth,
1359		    ButtonHeight);
1360}
1361
1362// Overridden
1363+ (BOOL) prefersTrackingUntilMouseUp
1364{
1365  return YES;
1366
1367  /* Needed to have the clickability of the button take in account when the tracking happens.
1368     This method is call by the NSControl -mouseDown: method with the code :
1369     [_cell trackMouse: e
1370		inRect: _bounds
1371	        ofView: self
1372          untilMouseUp: [[_cell class] prefersTrackingUntilMouseUp]] */
1373}
1374
1375- (NSSize) cellSize
1376{
1377  NSSize textSize;
1378  NSSize buttonSize;
1379  NSSize mySize;
1380
1381  /* Simple version takes the size from text field. A more useful one could
1382     loop over the strings of the combo box and calculate the maximal width of
1383     all strings. */
1384  textSize = [super cellSize];
1385  // Or should we use the hard coded values from above here?
1386  buttonSize = [_buttonCell cellSize];
1387
1388  mySize.height = MAX(textSize.height, buttonSize.height);
1389  mySize.width = textSize.width + BorderSize + buttonSize.width;
1390
1391  return mySize;
1392}
1393
1394- (void) drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
1395{
1396  NSRect rect = cellFrame;
1397
1398  // FIX ME: Is this test case below with the method call really needed ?
1399  if ([GSCurrentContext() isDrawingToScreen])
1400    {
1401      [super drawInteriorWithFrame: textCellFrameFromRect(rect)
1402			    inView: controlView];
1403      [_buttonCell drawWithFrame: buttonCellFrameFromRect(rect)
1404		          inView: controlView];
1405    }
1406  else
1407    {
1408      [super drawInteriorWithFrame: rect inView: controlView];
1409    }
1410
1411  // Used by GSComboWindow to appear in the right position
1412  _lastValidFrame = cellFrame;
1413}
1414
1415- (void) highlight: (BOOL)flag
1416	 withFrame: (NSRect)cellFrame
1417	    inView: (NSView *)controlView
1418{
1419  NSRect rect = cellFrame;
1420
1421  // FIX ME: Is this test case below with the method call really needed ?
1422  if ([GSCurrentContext() isDrawingToScreen])
1423    {
1424      [super highlight: flag
1425	     withFrame: textCellFrameFromRect(rect)
1426	        inView: controlView];
1427      [_buttonCell highlight: flag
1428		   withFrame: buttonCellFrameFromRect(rect)
1429		      inView: controlView];
1430    }
1431  else
1432    {
1433      [super highlight: flag withFrame: rect inView: controlView];
1434    }
1435}
1436
1437/** Overrides NSCell <code>trackMouse:inRect:ofView:untilMouseUp:</code> method to establish a
1438 * new method behavior.
1439 * In the case <var>flag</var> is NO, returns NO when the mouse down occurs in the text
1440 * cell part or when the mouse down occurs in the button cell part followed by a
1441 * mouse up outside, otherwise returns YES (when both the mouse down and the
1442 * mouse up occurs in the button cell part).
1443 * In the case <var>flag</var> is YES, returns NO when the mouse occurs in the text
1444 * cell part, otherwise returns YES (when the mouse down occurs in the button cell
1445 * part).
1446 */
1447- (BOOL) trackMouse: (NSEvent *)theEvent
1448	     inRect: (NSRect)cellFrame
1449	     ofView: (NSView *)controlView
1450       untilMouseUp: (BOOL)flag
1451{
1452  NSPoint point;
1453  BOOL isFlipped = [controlView isFlipped];
1454  NSRect buttonRect = buttonCellFrameFromRect(cellFrame);
1455  NSRect textRect = textCellFrameFromRect(cellFrame);
1456  BOOL result = NO;
1457
1458  // FIXME: May be that should be set by NSActionCell
1459  if (_control_view != controlView)
1460    _control_view = controlView;
1461
1462  // Used by GSComboWindow to appear in the right position
1463  _lastValidFrame = cellFrame;
1464  point = [controlView convertPoint: [theEvent locationInWindow]
1465			   fromView: nil];
1466
1467  if (NSMouseInRect(point, textRect, isFlipped))
1468    {
1469      return NO;
1470    }
1471  else if (NSMouseInRect(point, buttonRect, isFlipped))
1472    {
1473      NSEvent *e = theEvent;
1474      BOOL isMouseUp = NO;
1475      NSUInteger eventMask = NSLeftMouseDownMask | NSLeftMouseUpMask
1476       | NSMouseMovedMask | NSLeftMouseDraggedMask | NSOtherMouseDraggedMask
1477       | NSRightMouseDraggedMask;
1478      NSPoint location;
1479
1480      while (isMouseUp == NO) // Loop until mouse goes up
1481        {
1482          location = [controlView convertPoint: [e locationInWindow] fromView: nil];
1483
1484	  // Ask the cell to track the mouse only when the mouse is within the cell
1485          if (NSMouseInRect(location, buttonRect, isFlipped))
1486	    {
1487	      [_buttonCell setHighlighted: YES];
1488	      [controlView setNeedsDisplayInRect: cellFrame];
1489
1490	      result = [_buttonCell trackMouse: e
1491		                        inRect: buttonRect
1492		                        ofView: controlView
1493		                  untilMouseUp: [NSButtonCell prefersTrackingUntilMouseUp]];
1494              isMouseUp = result;
1495
1496	      [_buttonCell setHighlighted: NO];
1497	      [controlView setNeedsDisplayInRect: cellFrame];
1498            }
1499
1500          if (isMouseUp == NO)
1501	    {
1502	      e = [NSApp nextEventMatchingMask: eventMask
1503			             untilDate: [NSDate distantFuture]
1504		  		        inMode: NSEventTrackingRunLoopMode
1505				       dequeue: YES];
1506
1507	      if ([e type] == NSLeftMouseUp)
1508	        isMouseUp = YES;
1509	    }
1510	}
1511
1512      if (flag)
1513        {
1514	  return YES;
1515	}
1516      else
1517        {
1518          return NO;
1519	}
1520    }
1521
1522  return NO; // Pathological case, normally never happens
1523}
1524
1525- (void) resetCursorRect: (NSRect)cellFrame inView: (NSView *)controlView
1526{
1527  [super resetCursorRect: textCellFrameFromRect(cellFrame)
1528	 inView: controlView];
1529}
1530
1531- (void) setEnabled: (BOOL)flag
1532{
1533  [_buttonCell setEnabled: flag];
1534  [super setEnabled: flag];
1535}
1536
1537// NSCoding
1538/**
1539 * Encodes the combo box cell using <var>encoder</var>. take note that when it
1540 * uses a data source, the data source is conditionally encoded.
1541 */
1542- (void) encodeWithCoder: (NSCoder *)coder
1543{
1544  [super encodeWithCoder: coder];
1545
1546  if ([coder allowsKeyedCoding])
1547    {
1548      [coder encodeBool: [self hasVerticalScroller] forKey: @"NSHasVerticalScroller"];
1549      [coder encodeInt: [self numberOfVisibleItems] forKey: @"NSVisibleItemCount"];
1550      [coder encodeBool: [self completes] forKey: @"NSCompletes"];
1551      [coder encodeDouble: _intercellSpacing.width forKey: @"NSIntercellSpacingWidth"];
1552      [coder encodeDouble: _intercellSpacing.height forKey: @"NSIntercellSpacingHeight"];
1553      [coder encodeDouble: [self itemHeight] forKey: @"NSRowHeight"];
1554      [coder encodeBool: [self usesDataSource] forKey: @"NSUsesDataSource"];
1555      [coder encodeObject: [self dataSource] forKey: @"NSDataSource"];
1556      [coder encodeObject: _popUpList forKey: @"NSPopUpListData"];
1557    }
1558  else
1559    {
1560      [coder encodeValueOfObjCType: @encode(id) at: &_popUpList];
1561      [coder encodeValueOfObjCType: @encode(BOOL) at: &_usesDataSource];
1562      [coder encodeValueOfObjCType: @encode(BOOL) at: &_hasVerticalScroller];
1563      [coder encodeValueOfObjCType: @encode(BOOL) at: &_completes];
1564      [coder encodeValueOfObjCType: @encode(BOOL) at: &_usesDataSource];
1565      [coder encodeValueOfObjCType: @encode(int) at: &_visibleItems];
1566      [coder encodeValueOfObjCType: @encode(NSSize) at: &_intercellSpacing];
1567      [coder encodeValueOfObjCType: @encode(float) at: &_itemHeight];
1568      [coder encodeValueOfObjCType: @encode(int) at: &_selectedItem];
1569
1570      if (_usesDataSource == YES)
1571	[coder encodeConditionalObject: _dataSource];
1572    }
1573}
1574
1575/**
1576 * Initializes the combo box cell with data linked to <var>decoder</var>. Take
1577 * note that when the decoded instance uses a data source,
1578 * <code>initWithCoder:<var> decodes the data source.
1579 * Finally, returns thr initialized object.
1580 */
1581- (id) initWithCoder: (NSCoder *)aDecoder
1582{
1583  self = [super initWithCoder: aDecoder];
1584  if (nil == self)
1585    return nil;
1586
1587  if ([aDecoder allowsKeyedCoding])
1588    {
1589      //id delegate = [aDecoder decodeObjectForKey: @"NSDelegate"];
1590      //id table = [aDecoder decodeObjectForKey: @"NSTableView"];
1591
1592      if ([aDecoder containsValueForKey: @"NSHasVerticalScroller"])
1593        {
1594	  [self setHasVerticalScroller: [aDecoder decodeBoolForKey:
1595						      @"NSHasVerticalScroller"]];
1596	}
1597      if ([aDecoder containsValueForKey: @"NSVisibleItemCount"])
1598        {
1599	  [self setNumberOfVisibleItems: [aDecoder decodeIntForKey:
1600						       @"NSVisibleItemCount"]];
1601	}
1602      if ([aDecoder containsValueForKey: @"NSCompletes"])
1603        {
1604          [self setCompletes: [aDecoder decodeBoolForKey: @"NSCompletes"]];
1605        }
1606      if ([aDecoder containsValueForKey: @"NSIntercellSpacingWidth"])
1607        {
1608          _intercellSpacing.width = [aDecoder decodeDoubleForKey:
1609                                                @"NSIntercellSpacingWidth"];
1610        }
1611      if ([aDecoder containsValueForKey: @"NSIntercellSpacingHeight"])
1612        {
1613          _intercellSpacing.height = [aDecoder decodeDoubleForKey:
1614                                                 @"NSIntercellSpacingHeight"];
1615        }
1616      if ([aDecoder containsValueForKey: @"NSRowHeight"])
1617        {
1618	  [self setItemHeight: [aDecoder decodeDoubleForKey:
1619                                           @"NSRowHeight"]];
1620	}
1621      if ([aDecoder containsValueForKey: @"NSUsesDataSource"])
1622        {
1623          [self setUsesDataSource: [aDecoder decodeBoolForKey:
1624                                               @"NSUsesDataSource"]];
1625        }
1626      if ([aDecoder containsValueForKey: @"NSDataSource"])
1627        {
1628          [self setDataSource: [aDecoder decodeObjectForKey: @"NSDataSource"]];
1629        }
1630      if ([aDecoder containsValueForKey: @"NSPopUpListData"])
1631        {
1632          ASSIGN(_popUpList, [aDecoder decodeObjectForKey: @"NSPopUpListData"]);
1633        }
1634    }
1635  else
1636    {
1637      BOOL dummy;
1638
1639      if ([aDecoder versionForClassName: @"NSComboBoxCell"] < 2)
1640        {
1641          // In previous version we decode _buttonCell, we just discard the decoded value here
1642          id previouslyEncodedButton;
1643          [aDecoder decodeValueOfObjCType: @encode(id) at: &previouslyEncodedButton];
1644        }
1645
1646      [aDecoder decodeValueOfObjCType: @encode(id) at: &_popUpList];
1647      RETAIN(_popUpList);
1648      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_usesDataSource];
1649      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_hasVerticalScroller];
1650      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_completes];
1651      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &dummy];
1652      [aDecoder decodeValueOfObjCType: @encode(int) at: &_visibleItems];
1653      [aDecoder decodeValueOfObjCType: @encode(NSSize) at: &_intercellSpacing];
1654      [aDecoder decodeValueOfObjCType: @encode(float) at: &_itemHeight];
1655      [aDecoder decodeValueOfObjCType: @encode(int) at: &_selectedItem];
1656
1657      if (_usesDataSource == YES)
1658	[self setDataSource: [aDecoder decodeObject]];
1659    }
1660
1661  [self _loadButtonCell];
1662
1663  return self;
1664}
1665
1666- (void) selectWithFrame: (NSRect)aRect
1667		  inView: (NSView *)controlView
1668		  editor: (NSText *)textObj
1669		delegate: (id)anObject
1670		   start: (NSInteger)selStart
1671		  length: (NSInteger)selLength
1672{
1673  [super selectWithFrame: textCellFrameFromRect(aRect)
1674                  inView: controlView
1675                  editor: textObj
1676                delegate: anObject
1677                   start: selStart
1678                  length: selLength];
1679
1680  [nc addObserver: self
1681         selector: @selector(textDidChange:)
1682	     name: NSTextDidChangeNotification
1683	   object: textObj];
1684  [nc addObserver: self
1685         selector: @selector(textViewDidChangeSelection:)
1686	     name: NSTextViewDidChangeSelectionNotification
1687	   object: textObj];
1688
1689  // This method is called when the cell obtains the focus;
1690  // don't know why the next method editWithFrame: is not called
1691}
1692
1693- (void) editWithFrame: (NSRect)frame
1694                inView: (NSView *)controlView
1695	        editor: (NSText *)textObj
1696	      delegate: (id)delegate
1697	         event: (NSEvent *)theEvent
1698{
1699  [super editWithFrame: textCellFrameFromRect(frame)
1700                inView: controlView
1701                editor: textObj
1702              delegate: delegate
1703                 event: theEvent];
1704
1705  /*
1706  [nc addObserver: self
1707         selector: @selector(textDidChange:)
1708	     name: NSTextDidChangeNotification
1709	   object: textObj];
1710  [nc addObserver: self
1711         selector: @selector(textViewDidChangeSelection:)
1712	     name: NSTextViewDidChangeSelectionNotification
1713	   object: textObj]; */
1714}
1715
1716- (void) endEditing: (NSText *)editor
1717{
1718  /* Close the pop up if it is still open. This may happen, e.g., when the
1719     user presses the Tab key to shift focus to a different cell or view. */
1720  if (_popup)
1721    [_popup onWindowEdited: nil];
1722
1723  [super endEditing: editor];
1724  [nc removeObserver: self name: NSTextDidChangeNotification object: editor];
1725  [nc removeObserver: self
1726                name: NSTextViewDidChangeSelectionNotification
1727	      object: editor];
1728}
1729
1730- (void) textViewDidChangeSelection: (NSNotification *)notification
1731{
1732  _prevSelectedRange = [[[notification userInfo]
1733    objectForKey: @"NSOldSelectedCharacterRange"] rangeValue];
1734}
1735
1736- (void) textDidChange: (NSNotification *)notification
1737{
1738  NSText *textObject = [notification object];
1739
1740  if ([self completes])
1741    {
1742      NSString *myString = [[textObject string] copy];
1743      NSString *more;
1744      NSUInteger myStringLength = [myString length];
1745      NSUInteger location, length;
1746      NSRange selectedRange = [textObject selectedRange];
1747
1748      if (myStringLength != 0
1749        && selectedRange.location == myStringLength
1750        && _prevSelectedRange.location < selectedRange.location)
1751        {
1752          more = [self completedString: myString];
1753          if ((more != nil) && [more isEqualToString: myString] == NO)
1754            {
1755	      [textObject setString: more];
1756	      location = myStringLength;
1757              length = [more length] - location;
1758	      [textObject setSelectedRange: NSMakeRange(location, length)];
1759	      [textObject scrollRangeToVisible: NSMakeRange(location, length)];
1760	    }
1761        }
1762      RELEASE(myString);
1763    }
1764}
1765
1766@end
1767
1768@implementation NSComboBoxCell (GNUstepPrivate)
1769
1770- (NSString *) _stringValueAtIndex: (NSInteger)index
1771{
1772  if (_usesDataSource == NO)
1773    {
1774      return [[self itemObjectValueAtIndex: index] description];
1775    }
1776  else
1777    {
1778      if (_dataSource == nil)
1779        {
1780	  NSLog(@"%@: No data source currently specified", self);
1781	  return nil;
1782	}
1783      else if ([_dataSource respondsToSelector:
1784			   @selector(comboBox:objectValueForItemAtIndex:)])
1785        {
1786	  return [[_dataSource comboBox: (NSComboBox *)[self controlView]
1787			       objectValueForItemAtIndex: index] description];
1788	}
1789      else if ([_dataSource respondsToSelector:
1790				@selector(comboBoxCell:objectValueForItemAtIndex:)])
1791        {
1792	  return [[_dataSource comboBoxCell: self
1793			      objectValueForItemAtIndex: index] description];
1794	}
1795    }
1796
1797  return nil;
1798}
1799
1800- (void) _performClickWithFrame: (NSRect)cellFrame
1801                         inView: (NSView *)controlView
1802{
1803  NSWindow *cvWindow = [controlView window];
1804  NSRect buttonRect = buttonCellFrameFromRect(cellFrame);
1805
1806  _control_view = controlView;
1807  [controlView lockFocus];
1808  [_buttonCell highlight: YES
1809	       withFrame: buttonRect
1810	          inView: controlView];
1811  [controlView unlockFocus];
1812  [cvWindow flushWindow];
1813
1814  [self _didClickWithinButton: self];
1815
1816  [controlView lockFocus];
1817  [_buttonCell highlight: NO
1818	       withFrame: buttonRect
1819	          inView: controlView];
1820  [controlView unlockFocus];
1821  [cvWindow flushWindow];
1822
1823}
1824
1825- (void) _didClickWithinButton: (id)sender
1826{
1827  NSView *controlView = [self controlView];
1828
1829  if ((_cell.is_disabled) || (controlView == nil))
1830    return;
1831
1832  [nc postNotificationName: NSComboBoxWillPopUpNotification
1833                    object: controlView
1834		  userInfo: nil];
1835
1836  _popup = [self _popUp];
1837  [_popup popUpForComboBoxCell: self];
1838  _popup = nil;
1839
1840  [nc postNotificationName: NSComboBoxWillDismissNotification
1841                    object: controlView
1842                  userInfo: nil];
1843}
1844
1845- (BOOL) _isWantedEvent: (NSEvent *)event
1846{
1847  NSPoint loc;
1848  NSWindow *window = [event window];
1849  NSView *controlView = [self controlView];
1850
1851  if (window == [[self controlView] window])
1852    {
1853      loc = [event locationInWindow];
1854      loc = [controlView convertPoint: loc fromView: nil];
1855      return NSMouseInRect(loc, [self _textCellFrame], [controlView isFlipped]);
1856    }
1857  else
1858    {
1859      return NO;
1860    }
1861}
1862
1863- (GSComboWindow *) _popUp
1864{
1865  return [GSComboWindow defaultPopUp];
1866}
1867
1868- (NSRect) _textCellFrame
1869{
1870  return textCellFrameFromRect(_lastValidFrame);
1871}
1872
1873- (void) _setSelectedItem: (NSInteger)index
1874{
1875  _selectedItem = index;
1876}
1877
1878- (void) _loadButtonCell
1879{
1880  _buttonCell = [[NSButtonCell alloc] initImageCell:
1881		    [NSImage imageNamed: @"NSComboArrow"]];
1882  [_buttonCell setImagePosition: NSImageOnly];
1883  [_buttonCell setButtonType: NSMomentaryPushButton];
1884  [_buttonCell setHighlightsBy: NSPushInCellMask];
1885  [_buttonCell setBordered: YES];
1886  [_buttonCell setTarget: self];
1887  [_buttonCell setAction: @selector(_didClickWithinButton:)];
1888  [_buttonCell setEnabled: [self isEnabled]];
1889}
1890
1891- (void) _selectCompleted
1892{
1893  NSString *more;
1894  NSUInteger index = NSNotFound;
1895
1896  more = [self completedString: [self stringValue]];
1897  if (_usesDataSource)
1898    {
1899      if (_dataSource == nil)
1900        {
1901	  NSLog(@"%@: No data source currently specified", self);
1902	}
1903      else
1904        {
1905	  if ([_dataSource respondsToSelector:
1906			       @selector(comboBoxCell:indexOfItemWithStringValue:)])
1907	    {
1908	      index = [_dataSource comboBoxCell: self
1909		     indexOfItemWithStringValue: more];
1910	    }
1911	}
1912    }
1913  else
1914    {
1915      index = [[self objectValues] indexOfObject: more];
1916    }
1917
1918  if (index != NSNotFound)
1919    {
1920      [self _setSelectedItem: index];
1921    }
1922  // Otherwise keep old selection
1923}
1924
1925@end
1926