1/** <title>NSPopUpButton</title>
2
3   <abstract>Popup list class</abstract>
4
5   Copyright (C) 1996 Free Software Foundation, Inc.
6
7   Author: Scott Christley <scottc@net-community.com>
8   Date: 1996
9   Author: Michael Hanni <mhanni@sprintmail.com>
10   Date: June 1999
11
12   This file is part of the GNUstep GUI Library.
13
14   This library is free software; you can redistribute it and/or
15   modify it under the terms of the GNU Lesser General Public
16   License as published by the Free Software Foundation; either
17   version 2 of the License, or (at your option) any later version.
18
19   This library is distributed in the hope that it will be useful,
20   but WITHOUT ANY WARRANTY; without even the implied warranty of
21   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
22   Lesser General Public License for more details.
23
24   You should have received a copy of the GNU Lesser General Public
25   License along with this library; see the file COPYING.LIB.
26   If not, see <http://www.gnu.org/licenses/> or write to the
27   Free Software Foundation, 51 Franklin Street, Fifth Floor,
28   Boston, MA 02110-1301, USA.
29*/
30
31#import <Foundation/NSKeyValueCoding.h>
32#import <Foundation/NSKeyValueObserving.h>
33#import <Foundation/NSValue.h>
34#import "AppKit/NSApplication.h"
35#import "AppKit/NSEvent.h"
36#import "AppKit/NSGraphics.h"
37#import "AppKit/NSKeyValueBinding.h"
38#import "AppKit/NSPopUpButton.h"
39#import "AppKit/NSPopUpButtonCell.h"
40#import "AppKit/NSMenu.h"
41#import "AppKit/NSMenuItem.h"
42#import "AppKit/NSMenuView.h"
43#import "AppKit/NSWindow.h"
44
45/*
46 * class variables
47 */
48Class _nspopupbuttonCellClass = 0;
49
50/*
51 * NSPopUpButton implementation
52 */
53
54@implementation NSPopUpButton
55
56/*
57 * Class methods
58 */
59+ (void) initialize
60{
61  if (self == [NSPopUpButton class])
62    {
63      // Initial version
64      [self setVersion: 1];
65      [self setCellClass: [NSPopUpButtonCell class]];
66
67      [self exposeBinding: NSSelectedIndexBinding];
68      [self exposeBinding: NSSelectedObjectBinding];
69      [self exposeBinding: NSSelectedTagBinding];
70      [self exposeBinding: NSSelectedValueBinding];
71      [self exposeBinding: NSContentValuesBinding];
72   }
73}
74
75+ (Class) cellClass
76{
77  return _nspopupbuttonCellClass;
78}
79
80+ (void) setCellClass: (Class)classId
81{
82  _nspopupbuttonCellClass = classId;
83}
84
85/*
86 * Initializing an NSPopUpButton
87 */
88- (id) init
89{
90  return [self initWithFrame: NSZeroRect pullsDown: NO];
91}
92
93- (id) initWithFrame: (NSRect)frameRect
94{
95  return [self initWithFrame: frameRect pullsDown: NO];
96}
97
98/** <p>Initialize and returns a new NSPopUpButton into the frame frameRect
99    and specified by flag if the NSPopUpButton is a pull-down list</p>
100    <p>See Also: -setPullsDown: [NSView-initWithFrame:]</p>
101 */
102- (id) initWithFrame: (NSRect)frameRect
103	   pullsDown: (BOOL)flag
104{
105  if ( ! ( self = [super initWithFrame: frameRect] ) )
106    return nil;
107
108  [self setPullsDown: flag];
109
110  return self;
111}
112
113
114/*
115In NSView, -menuForEvent: returns [self menu] as the context menu of the
116view. Since our -menu returns the menu for our pop-up, we need to override
117this to return nil to indicate that we have no context menu.
118*/
119- (NSMenu *)menuForEvent:(NSEvent *)theEvent
120{
121  return nil;
122}
123
124
125- (void) setMenu: (NSMenu*)menu
126{
127  [_cell setMenu: menu];
128}
129
130- (NSMenu*) menu
131{
132  return [_cell menu];
133}
134
135/**<p>Sets whether the NSPopUpButton's cell has a pulls-down list ( YES )
136   or a pop-up list (NO)  </p> <p>See Also: -pullsDown
137   [NSPopUpButtonCell-setPullsDown:]</p>
138*/
139
140- (void) setPullsDown: (BOOL)flag
141{
142  [_cell setPullsDown: flag];
143}
144/** <p>Returns whether the NSPopUpButton's cell has a pulls-down list ( YES )
145    or a pop-up list (NO) </p>
146    <p>See Also: -setPullsDown: [NSPopUpButtonCell-pullsDown]</p>
147 */
148- (BOOL) pullsDown
149{
150  return [_cell pullsDown];
151}
152
153- (void) setAutoenablesItems: (BOOL)flag
154{
155  [_cell setAutoenablesItems: flag];
156}
157
158- (BOOL) autoenablesItems
159{
160  return [_cell autoenablesItems];
161}
162
163/** <p>Inserts a new item with title as its title at the end of the list and
164    synchronizes the NSPopUpButton's title with the title of the selected item.
165    </p><p>See Also: [NSPopUpButtonCell-addItemWithTitle:]
166    -synchronizeTitleAndSelectedItem</p>
167 */
168- (void) addItemWithTitle: (NSString *)title
169{
170  [_cell addItemWithTitle: title];
171
172  [self synchronizeTitleAndSelectedItem];
173}
174
175/** <p>Inserts a new list of items with titles as titles at the end of the list
176    and synchronizes the NSPopUpButton's title with the title of the selected
177    item.</p><p>See Also: [NSPopUpButtonCell-addItemsWithTitles:]
178    -synchronizeTitleAndSelectedItem</p>
179 */
180- (void) addItemsWithTitles: (NSArray*)itemTitles
181{
182  [_cell addItemsWithTitles: itemTitles];
183
184  [self synchronizeTitleAndSelectedItem];
185}
186
187/** <p>Inserts a new item with title as its title at the specified index
188    and synchronizes the NSPopUpButton's title with the title of the selected
189    item.</p><p>See Also: [NSPopUpButtonCell-insertItemWithTitle:atIndex:]
190    -synchronizeTitleAndSelectedItem</p>
191 */
192- (void) insertItemWithTitle: (NSString*)title
193		     atIndex: (NSInteger)index
194{
195  [_cell insertItemWithTitle: title
196		     atIndex: index];
197
198  [self synchronizeTitleAndSelectedItem];
199}
200
201/** <p>Removes all items from the item list and synchronizes the
202    NSPopUpButton's title with the title of the selected</p>
203    <p>See Also: [NSPopUpButtonCell-removeAllItems] -removeItemWithTitle:
204    -synchronizeTitleAndSelectedItem</p>
205*/
206- (void) removeAllItems
207{
208  [_cell removeAllItems];
209
210  [self synchronizeTitleAndSelectedItem];
211}
212
213/** <p>Removes the item specified with title as its title from the item list
214    and synchronizes the NSPopUpButton's title with the title of the selected
215    </p><p>See Also: [NSPopUpButtonCell-removeItemWithTitle:]
216    -removeAllItems -removeItemAtIndex: -synchronizeTitleAndSelectedItem</p>
217*/
218- (void) removeItemWithTitle: (NSString*)title
219{
220  [_cell removeItemWithTitle: title];
221
222  [self synchronizeTitleAndSelectedItem];
223}
224
225/** <p>Removes the item at the specified index index from the item list
226    and synchronizes the NSPopUpButton's title with the title of the selected
227    </p><p>See Also: [NSPopUpButtonCell-removeItemAtIndex:]
228    -removeAllItems -removeItemWithTitle: -synchronizeTitleAndSelectedItem</p>
229*/
230- (void) removeItemAtIndex: (NSInteger)index
231{
232  [_cell removeItemAtIndex: index];
233
234  [self synchronizeTitleAndSelectedItem];
235}
236
237/** <p>Returns the selected item</p>
238    <p>See Also: [NSPopUpButtonCell-selectedItem]</p>
239 */
240- (id <NSMenuItem>) selectedItem
241{
242  return [_cell selectedItem];
243}
244
245/** <p>Returns the title of the selected item</p>
246    <p>See Also: [NSPopUpButtonCell-titleOfSelectedItem]</p>
247 */
248- (NSString*) titleOfSelectedItem
249{
250  return [_cell titleOfSelectedItem];
251}
252
253/**<p>Returns the index of the selected item</p>
254   <p>See Also: [NSPopUpButtonCell-indexOfSelectedItem]</p>
255 */
256- (NSInteger) indexOfSelectedItem
257{
258  return [_cell indexOfSelectedItem];
259}
260
261/**<p>Returns the tag of the selected item</p>
262   <p>See Also: [NSPopUpButtonCell-indexOfSelectedItem]</p>
263*/
264- (NSInteger) selectedTag
265{
266  return [[_cell selectedItem] tag];
267}
268
269- (void) selectItem: (id <NSMenuItem>)anObject
270{
271  [_cell selectItem: anObject];
272  [self synchronizeTitleAndSelectedItem];
273}
274
275/**<p>Select the item at index <var>index</var> and synchronizes the
276   NSPopUpButton's title with the title of the selected</p><p>See Also:
277   [NSPopUpButtonCell-selectItemAtIndex:] -synchronizeTitleAndSelectedItem</p>
278 */
279- (void) selectItemAtIndex: (NSInteger)index
280{
281  [_cell selectItemAtIndex: index];
282  [self synchronizeTitleAndSelectedItem];
283}
284
285/**<p>Select the item with title <var>title</var> and synchronizes the
286   NSPopUpButton's title with the title of the selected</p><p>See Also:
287   [NSPopUpButtonCell-selectItemWithTitle:]
288   -synchronizeTitleAndSelectedItem</p>
289 */
290- (void) selectItemWithTitle: (NSString*)title
291{
292  [_cell selectItemWithTitle: title];
293  [self synchronizeTitleAndSelectedItem];
294}
295
296- (BOOL) selectItemWithTag: (NSInteger)tag
297{
298   NSInteger index = [self indexOfItemWithTag: tag];
299
300   if (index >= 0)
301     {
302       [self selectItemAtIndex: index];
303       return YES;
304     }
305   else
306     {
307       return NO;
308     }
309}
310
311
312/** <p>Returns the number of items in the item list</p>
313    <p>See Also: [NSPopUpButtonCell-numberOfItems]</p>
314 */
315- (NSInteger) numberOfItems
316{
317  return [_cell numberOfItems];
318}
319
320- (NSArray*) itemArray
321{
322  return [_cell itemArray];
323}
324
325/**<p>Returns the NSMenuItem at index index or nil if index is out of
326   range</p><p>See Also: [NSPopUpButtonCell-itemAtIndex:] </p>
327 */
328- (id <NSMenuItem>) itemAtIndex: (NSInteger)index
329{
330  return [_cell itemAtIndex: index];
331}
332
333/** <p>Returns the item's title at index <var>index</var></p>
334 */
335- (NSString*) itemTitleAtIndex: (NSInteger)index
336{
337  return [_cell itemTitleAtIndex: index];
338}
339
340/**<p>Returns an array containing the items's titles</p>
341 */
342- (NSArray*) itemTitles
343{
344  return [_cell itemTitles];
345}
346
347/**<p>Returns the NSMenuItem with title as its title</p>
348 */
349- (id <NSMenuItem>) itemWithTitle: (NSString*)title
350{
351  return [_cell itemWithTitle: title];
352}
353
354/**<p> Returns the last NSMenuItem of the list</p>
355 */
356- (id <NSMenuItem>) lastItem
357{
358  return [_cell lastItem];
359}
360
361- (NSInteger) indexOfItem: (id <NSMenuItem>)anObject
362{
363  return [_cell indexOfItem: anObject];
364}
365
366/**<p>Returns the index of the item with tag as its tag. Returns -1
367   if the cell is not found</p><p>See Also:
368   [NSPopUpButtonCell-indexOfItemWithTag:] -indexOfItemWithTitle:
369   -indexOfItemWithRepresentedObject:</p>
370*/
371- (NSInteger) indexOfItemWithTag: (NSInteger)tag
372{
373  return [_cell indexOfItemWithTag: tag];
374}
375
376/**<p>Returns the index of the item with title as its title. Returns -1
377   if the cell is not found</p><p>See Also:
378   [NSPopUpButtonCell-indexOfItemWithTitle:] -indexOfItemWithTag:
379   -indexOfItemWithRepresentedObject:</p>
380*/
381- (NSInteger) indexOfItemWithTitle: (NSString*)title
382{
383  return [_cell indexOfItemWithTitle: title];
384}
385
386- (NSInteger) indexOfItemWithRepresentedObject: (id)anObject
387{
388  return [_cell indexOfItemWithRepresentedObject: anObject];
389}
390
391- (NSInteger) indexOfItemWithTarget: (id)target
392		    andAction: (SEL)actionSelector
393{
394  return [_cell indexOfItemWithTarget: target andAction: actionSelector];
395}
396
397- (void) setPreferredEdge: (NSRectEdge)edge
398{
399  [_cell setPreferredEdge: edge];
400}
401
402- (NSRectEdge) preferredEdge
403{
404  return [_cell preferredEdge];
405}
406
407- (void) setTitle: (NSString*)aString
408{
409  [_cell setTitle: aString];
410  [self synchronizeTitleAndSelectedItem];
411}
412
413- (void) synchronizeTitleAndSelectedItem
414{
415  [_cell synchronizeTitleAndSelectedItem];
416  [self setNeedsDisplay: YES];
417}
418
419- (BOOL) resignFirstResponder
420{
421  [_cell dismissPopUp];
422
423  return [super resignFirstResponder];
424}
425
426- (BOOL) performKeyEquivalent: (NSEvent*)theEvent
427{
428  NSMenu     *m = [self menu];
429  NSMenuItem *oldSelectedItem = (NSMenuItem *)[_cell selectedItem];
430
431  if (m != nil)
432    {
433      if ([m performKeyEquivalent: theEvent])
434	{
435	  // pullsDown does not change selected item
436	  if ([_cell pullsDown])
437	    {
438	      [self selectItem: oldSelectedItem];
439	    }
440	  else
441	    {
442	      /* If the key equivalent was performed, redisplay ourselves
443	       * to account for potential changes in the selected item.
444	       */
445	      [self setNeedsDisplay: YES];
446	    }
447	  return YES;
448	}
449    }
450  return NO;
451}
452
453- (void) mouseDown: (NSEvent*)theEvent
454{
455  [_cell trackMouse: theEvent
456	     inRect: [self bounds]
457	     ofView: self
458       untilMouseUp: YES];
459}
460
461- (void) keyDown: (NSEvent*)theEvent
462{
463  // FIXME: This method also handles the key events for the popup menu window,
464  // as menu windows cannot become key window.
465  if ([self isEnabled])
466    {
467      NSString *characters = [theEvent characters];
468      unichar character = 0;
469
470      if ([characters length] > 0)
471	{
472	  character = [characters characterAtIndex: 0];
473	}
474
475      switch (character)
476	{
477	case NSNewlineCharacter:
478	case NSEnterCharacter:
479	case NSCarriageReturnCharacter:
480	  /* Handle Enter and Return keys only when the menu is visible.
481	     The button's action to pop up the menu is initiated only by
482	     the Space key similar to other buttons. */
483	  {
484	    NSMenuView *menuView = [[_cell menu] menuRepresentation];
485	    if ([[menuView window] isVisible] == NO)
486	      break;
487	  }
488	case ' ':
489	  {
490	    NSInteger selectedIndex;
491	    NSMenuView *menuView;
492
493	    // Beep, as on OS, and then return.
494	    if ([[_cell menu] numberOfItems] == 0)
495	      {
496		NSBeep();
497		return;
498	      }
499
500	    menuView = [[_cell menu] menuRepresentation];
501	    if ([[menuView window] isVisible] == NO)
502	      {
503		// Attach the popUp
504		[_cell attachPopUpWithFrame: _bounds
505		       inView: self];
506	      }
507	    else
508	      {
509		selectedIndex = [menuView highlightedItemIndex];
510		if (selectedIndex >= 0)
511		  {
512		    [[_cell menu] performActionForItemAtIndex: selectedIndex];
513		  }
514	      }
515	  }
516	  return;
517	case '\e':
518	  [_cell dismissPopUp];
519	  return;
520	case NSUpArrowFunctionKey:
521	  {
522	    NSMenuView *menuView;
523	    NSInteger selectedIndex, numberOfItems;
524
525	    menuView = [[_cell menu] menuRepresentation];
526	    selectedIndex = [menuView highlightedItemIndex];
527	    numberOfItems = [self numberOfItems];
528
529	    switch (selectedIndex)
530	      {
531	      case -1:
532		selectedIndex = numberOfItems - 1;
533		break;
534	      case 0:
535		return;
536	      default:
537		selectedIndex--;
538		break;
539	      }
540
541	    [menuView setHighlightedItemIndex: selectedIndex];
542	  }
543	  return;
544	case NSDownArrowFunctionKey:
545	  {
546	    NSMenuView *menuView;
547	    NSInteger selectedIndex, numberOfItems;
548
549	    menuView = [[_cell menu] menuRepresentation];
550	    selectedIndex = [menuView highlightedItemIndex];
551	    numberOfItems = [self numberOfItems];
552
553	    if (selectedIndex < numberOfItems-1)
554	      [menuView setHighlightedItemIndex: selectedIndex + 1];
555	  }
556	  return;
557	}
558    }
559
560  [super keyDown: theEvent];
561}
562
563- (void) setValue: (id)anObject forKey: (NSString*)aKey
564{
565  if ([aKey isEqual: NSSelectedIndexBinding])
566    {
567      [self selectItemAtIndex: [anObject intValue]];
568    }
569  else if ([aKey isEqual: NSSelectedTagBinding])
570    {
571      [self selectItemWithTag: [anObject integerValue]];
572    }
573  else if ([aKey isEqual: NSSelectedObjectBinding])
574    {
575      // FIXME: This looks wrong to me
576      [self selectItemWithTag: [anObject intValue]];
577    }
578  else if ([aKey isEqual: NSSelectedValueBinding])
579    {
580      [self selectItemWithTitle: anObject];
581    }
582  else if ([aKey isEqual: NSContentValuesBinding])
583    {
584      [self removeAllItems];
585      [self addItemsWithTitles: (NSArray*)anObject];
586    }
587  else
588    {
589      [super setValue: anObject forKey: aKey];
590    }
591}
592
593- (id) valueForKey: (NSString*)aKey
594{
595  if ([aKey isEqual: NSSelectedIndexBinding])
596    {
597      return [NSNumber numberWithInt: [self indexOfSelectedItem]];
598    }
599  else if ([aKey isEqual: NSSelectedTagBinding])
600    {
601      return [NSNumber numberWithInteger: [self selectedTag]];
602    }
603  else if ([aKey isEqual: NSSelectedObjectBinding])
604    {
605      // FIXME
606      return [NSNumber numberWithInt: [self selectedTag]];
607    }
608  else if ([aKey isEqual: NSSelectedValueBinding])
609    {
610      return [self titleOfSelectedItem];
611    }
612  else if ([aKey isEqual: NSContentValuesBinding])
613    {
614      return [self itemTitles];
615    }
616  else
617    {
618      return [super valueForKey: aKey];
619    }
620}
621
622@end
623