1/** <title>NSComboBox</title>
2
3   Copyright (C) 1999 Free Software Foundation, Inc.
4
5   Author: Gerrit van Dyk <gerritvd@decillion.net>
6   Date: 1999
7
8   This file is part of the GNUstep GUI Library.
9
10   This library is free software; you can redistribute it and/or
11   modify it under the terms of the GNU Lesser General Public
12   License as published by the Free Software Foundation; either
13   version 2 of the License, or (at your option) any later version.
14
15   This library is distributed in the hope that it will be useful,
16   but WITHOUT ANY WARRANTY; without even the implied warranty of
17   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
18   Lesser General Public License for more details.
19
20   You should have received a copy of the GNU Lesser General Public
21   License along with this library; see the file COPYING.LIB.
22   If not, see <http://www.gnu.org/licenses/> or write to the
23   Free Software Foundation, 51 Franklin Street, Fifth Floor,
24   Boston, MA 02110-1301, USA.
25*/
26
27#import <Foundation/NSNotification.h>
28#import <Foundation/NSString.h>
29#import "AppKit/NSComboBox.h"
30#import "AppKit/NSComboBoxCell.h"
31#import "AppKit/NSEvent.h"
32#import "AppKit/NSTextView.h"
33
34/*
35 * Class variables
36 */
37static Class usedCellClass;
38static Class comboBoxCellClass;
39static NSNotificationCenter *nc;
40
41/*
42 * Declaration of private cell method
43 */
44
45@interface NSComboBoxCell (GNUstepPrivate)
46- (void) _performClickWithFrame: (NSRect)cellFrame
47			 inView: (NSView *)controlView;
48@end
49
50/**
51 <unit>
52 <heading>Class Description</heading>
53 <p>An NSComboBox is what we can call a completion/choices box, derived from
54 NSTextField, it allows you to enter text like in a text field but also to click
55 in the ellipsis button (indicating the fact other user inputs are possible) on
56 the right of it to obtain a list of choices, you can use them as the text field
57 value by selecting a row in this list. You can also obtain direct completion
58 when it  is enabled via <code>setCompletes:</code> to get a suggested text
59 field value updated as you type.</p>
60 <p>Like other NSControl classes, NSComboBox is a wrapper around a core piece which
61 implements the combo box behavior, a cell, which is in this case an
62 NSComboBoxCell.</p>
63 </unit>
64*/
65
66/**
67<p>No special instructions to use NSComboBox or text to detail the implementation.</p>
68 */
69@implementation NSComboBox
70
71+ (void) initialize
72{
73  if (self == [NSComboBox class])
74    {
75       [self setVersion: 1];
76       comboBoxCellClass = [NSComboBoxCell class];
77       usedCellClass = comboBoxCellClass;
78       nc = [NSNotificationCenter defaultCenter];
79    }
80}
81
82/*
83 * Setting the Cell class
84 */
85+ (Class) cellClass
86{
87  return usedCellClass;
88}
89
90+ (void) setCellClass: (Class)factoryId
91{
92  usedCellClass = factoryId ? factoryId : comboBoxCellClass;
93}
94
95/**
96 * Returns YES when the combo box cell displays a vertical scroller for its
97 * list, returns NO otherwise.
98 * Take note that the scroller will be displayed even when the sum of the items
99 * height in the list is inferior to the minimal height of the list displayed
100 * area.
101 */
102- (BOOL)hasVerticalScroller
103{
104  return [_cell hasVerticalScroller];
105}
106
107/**
108 * Sets whether the combo box cell list displays a vertical scroller, by default
109 * it is the case. When <var>flag</var> is NO and the combo cell list has more
110 * items (either in its default list or from its data source) than the number
111 * returned by <code>numberOfVisibleItems</code>, only a subset of them will be
112 * displayed. Uses scroll related methods to position this subset in the combo
113 * box cell list.
114 * Take note that the scroller will be displayed even when the sum of the items
115 * height in the list is inferior to the minimal height of the list displayed
116 * area.
117 */
118- (void)setHasVerticalScroller:(BOOL)flag
119{
120  [_cell setHasVerticalScroller:flag];
121}
122
123/**
124 * Returns the width and the height (as the values of an NSSize variable)
125 * between each item of the combo box cell list.
126 */
127- (NSSize)intercellSpacing
128{
129  return [_cell intercellSpacing];
130}
131
132/**
133 * Sets the width and the height between each item of the combo box cell list to
134 * the values in <var>aSize</var>.
135 */
136- (void)setIntercellSpacing:(NSSize)aSize
137{
138  [_cell setIntercellSpacing:aSize];
139}
140
141/**
142 * Returns the height of the items in the combo box cell list.
143 */
144- (CGFloat)itemHeight
145{
146  return [_cell itemHeight];
147}
148
149/**
150 * Sets the height of the items in the combo box cell list to
151 * <var>itemHeight</var>.
152 */
153- (void)setItemHeight:(CGFloat)itemHeight
154{
155  [_cell setItemHeight:itemHeight];
156}
157
158/**
159 * Returns the maximum number of allowed items to be displayed in the combo box
160 * cell list.
161 */
162- (NSInteger)numberOfVisibleItems
163{
164  return [_cell numberOfVisibleItems];
165}
166
167/**
168 * Sets the maximum number of allowed items to be displayed in the combo box
169 * cell list.
170 */
171- (void)setNumberOfVisibleItems:(NSInteger)visibleItems
172{
173  [_cell setNumberOfVisibleItems:visibleItems];
174}
175
176/**
177 * Marks the combo box cell in order to have its items list reloaded in the
178 * case it uses a data source, and to have it redisplayed.
179 */
180- (void)reloadData
181{
182  [_cell reloadData];
183}
184
185/**
186 * Informs the combo box cell that the number of items in its data source has
187 * changed, in order to permit to the scrollers in its displayed list being
188 * updated without needing the reload of the data.
189 * It is recommended to use this method with a data source that continually
190 * receives data in the background, to keep the the combo box cell responsive to
191 * the user while the data is received.
192 * Take a look at the <code>NSComboBoxDataSource</code> informal protocol
193 * specification to know more on the messages NSComboBox sends to its data
194 * source.
195 */
196- (void)noteNumberOfItemsChanged
197{
198  [_cell noteNumberOfItemsChanged];
199}
200
201/**
202 * Returns YES when the combo box cell uses a data source (which is external) to
203 * populate its items list, otherwise returns NO in the case it uses its default
204 * list.
205 */
206- (BOOL)usesDataSource
207{
208  return [_cell usesDataSource];
209}
210
211/**
212 * Sets according to <var>flag</var> whether the combo box cell uses a data
213 * source (which is external) to populate its items list.
214 */
215- (void)setUsesDataSource:(BOOL)flag
216{
217  [_cell setUsesDataSource:flag];
218}
219
220/**
221 * Scrolls the combo box cell list vertically in order to have the item at
222 * <var>index</var> in the closest position relative to the top. There is no
223 * need to have the list displayed when this method is invoked.
224 */
225- (void)scrollItemAtIndexToTop:(NSInteger)index
226{
227  [_cell scrollItemAtIndexToTop:index];
228}
229
230/**
231 * Scrolls the combo box cell list vertically in order to have the item at
232 * <var>index</var> visible. There is no need to have the list displayed when
233 * this method is invoked.
234 */
235- (void)scrollItemAtIndexToVisible:(NSInteger)index
236{
237  [_cell scrollItemAtIndexToVisible:index];
238}
239
240/**
241 * Selects the combo box cell list row at <var>index</var>.
242 * Take note no changes occurs in the combo box cell list when this method is
243 * called.
244 * Posts an NSComboBoxSelectionDidChangeNotification to the default notification
245 * center when there is a new selection different from the previous one.
246 */
247- (void)selectItemAtIndex:(NSInteger)index
248{
249  [_cell selectItemAtIndex:index];
250}
251
252/**
253 * Deselects the combo box cell list row at <var>index</var> in the case this
254 * row is selected.
255 * Posts an NSComboBoxSelectionDidChangeNotification to the default notification
256 * center, when there is a new selection.
257 */
258- (void)deselectItemAtIndex:(NSInteger)index
259{
260  [_cell deselectItemAtIndex:index];
261}
262
263/**
264 * Returns the index of the selected item in the combo box cell list or -1 when
265 * there is no selection, the selected item can be related to the data source
266 * object in the case <code>usesDataSource</code> returns YES else to the
267 * default items list.
268 */
269- (NSInteger)indexOfSelectedItem
270{
271  return [_cell indexOfSelectedItem];
272}
273
274/**
275 * Returns the number of items in the the combo box cell list, the numbers of
276 * items can be be related to the data source object in the case
277 * <code>usesDataSource</code> returns YES else to the default items list.
278 */
279- (NSInteger)numberOfItems
280{
281  return [_cell numberOfItems];
282}
283
284/**
285 * Returns the combo box cell data source object which is reponsible to provide
286 * the data to be displayed. To know how to implement a data source object,
287 * take a  look at the NSComboBoxDataSource informal protocol description. In
288 * the case <code>usesDataSource</code> returns NO, this method logs a warning.
289 */
290- (id)dataSource
291{
292  return [_cell dataSource];
293}
294
295/**
296 * Sets the combo box cell data source to <var>aSource</var>. Just calling this
297 * method doesn't set <code>usesDataSource</code> to return YES, you must call
298 * <code>setUsesDataSource:</code> with YES before or a warning will be logged.
299 * To know how to implement a data source objects, take a  look at the
300 * NSComboBoxDataSource informal protocol description. When <var>aSource</var>
301 * doesn't respond to the methods <code>numberOfItemsInComboBox:</code>
302 * <code>comboBox:objectValueForItemAtIndex:</code>, this method
303 * logs a warning.
304 */
305- (void)setDataSource:(id)aSource
306{
307  [_cell setDataSource:aSource];
308}
309
310/**
311 * Adds an item to the combo box cell default items list which is used when
312 * <code>usesDataSource</code> returns NO. In the case
313 * <code>usesDataSource</code> returns YES, this method logs a warning.
314 */
315- (void)addItemWithObjectValue:(id)object
316{
317  [_cell addItemWithObjectValue:object];
318}
319
320/**
321 * Adds several items in an array to the combo box cell default items list which
322 * is used when <code>usesDataSource</code> returns NO. In the case
323 * <code>usesDataSource</code> returns YES, this method logs a warning.
324 */
325- (void)addItemsWithObjectValues:(NSArray *)objects
326{
327  [_cell addItemsWithObjectValues:objects];
328}
329
330/**
331 * Inserts an item in the combo box cell default items list which
332 * is used when <code>usesDataSource</code> returns NO. In the case
333 * <code>usesDataSource</code> returns YES, this method logs a warning.
334 */
335- (void)insertItemWithObjectValue:(id)object atIndex:(NSInteger)index
336{
337  [_cell insertItemWithObjectValue:object atIndex:index];
338}
339
340/**
341 * Removes an item in the combo box cell default items list which
342 * is used when <code>usesDataSource</code> returns NO. In the case
343 * <code>usesDataSource</code> returns YES, this method logs a warning.
344 */
345- (void)removeItemWithObjectValue:(id)object
346{
347  [_cell removeItemWithObjectValue:object];
348}
349
350/**
351 * Removes the item with the specified <var>index</var> in the combo box cell
352 * default items list which is used when <code>usesDataSource</code> returns NO.
353 * In the case <code>usesDataSource</code> returns YES, this method logs a warning.
354 */
355- (void)removeItemAtIndex:(NSInteger)index
356{
357  [_cell removeItemAtIndex:index];
358}
359
360/**
361 * Removes all the items in the combo box cell default items list which is used
362 * when <code>usesDataSource</code> returns NO. In the case
363 * <code>usesDataSource</code> returns YES, this method logs a warning.
364 */
365- (void)removeAllItems
366{
367  [_cell removeAllItems];
368}
369
370/**
371 * Selects the first item in the default combo box cell list which is equal to
372 * <var>object</var>. In the case <code>usesDataSource</code> returns YES, this
373 * method logs a warning.
374 * Take note that this method doesn't update the text field part value.
375 * Posts an NSComboBoxSelectionDidChange notification to the default
376 * notification center when the new selection is different than the previous
377 * one.
378 */
379- (void)selectItemWithObjectValue:(id)object
380{
381  [_cell selectItemWithObjectValue:object];
382}
383
384/**
385 * Returns the object value at <var>index</var> within combo box cell default
386 * items list. When the index is beyond the end of the list, an NSRangeException is
387 * raised. In the case <code>usesDataSource</code> returns YES, this method logs
388 * a warning.
389 */
390- (id)itemObjectValueAtIndex:(NSInteger)index
391{
392  return [_cell itemObjectValueAtIndex:index];
393}
394
395/**
396 * Returns the object value of the selected item in the combo box cell default
397 * items list or nil when there is no selection. In the case
398 * <code>usesDataSource</code> returns YES, this method logs a warning.
399 */
400- (id)objectValueOfSelectedItem
401{
402  return [_cell objectValueOfSelectedItem];
403}
404
405/**
406 * Returns the lowest index associated with a value in the combo box
407 * cell default items list, which is equal to <var>object</var>, and returns
408 * NSNotFound when there is no such value. In the case
409 * <code>usesDataSource</code> returns YES, this method logs a warning.
410 */
411- (NSInteger)indexOfItemWithObjectValue:(id)object
412{
413  return [_cell indexOfItemWithObjectValue:object];
414}
415
416/**
417 * Returns the combo box cell default items list in an array.
418 */
419- (NSArray *)objectValues
420{
421  return [_cell objectValues];
422}
423
424/**
425 * Returns YES when the combo box cell automatic completion is active, returns
426 * NO otherwise.
427 * Take a look at the <code>setCompletes:</code> method documentation to know
428 * how the automatic completion works.
429 */
430- (BOOL)completes
431{
432  return [_cell completes];
433}
434
435/**
436 * Sets whether the combo box cell automatic completion is active or not.
437 * The automatic completion tries to complete what the user types in the text
438 * field part, it tries to complete only when the the user adds characters at
439 * the end of the string, not when it deletes characters or when the insertion
440 * point precedes the end of the string.
441 * To do the automatic completion, the <code>completedString:</code> method is
442 * called, and when the returned string is longer than the current one in the text
443 * field, the completion occurs and the completed part gets selected.
444 */
445- (void)setCompletes:(BOOL)completes
446{
447  [_cell setCompletes: completes];
448}
449
450- (BOOL) isButtonBordered
451{
452  return [_cell isButtonBordered];
453}
454
455- (void) setButtonBordered:(BOOL)flag
456{
457  [_cell setButtonBordered: flag];
458}
459
460- (void) setDelegate: (id)anObject
461{
462  [super setDelegate: anObject];
463
464#define SET_DELEGATE_NOTIFICATION(notif_name) \
465  if ([_delegate respondsToSelector: @selector(comboBox##notif_name:)]) \
466    [nc addObserver: _delegate \
467           selector: @selector(comboBox##notif_name:) \
468               name: NSComboBox##notif_name##Notification object: self]
469
470  SET_DELEGATE_NOTIFICATION(SelectionDidChange);
471  SET_DELEGATE_NOTIFICATION(SelectionIsChanging);
472  SET_DELEGATE_NOTIFICATION(WillPopUp);
473  SET_DELEGATE_NOTIFICATION(WillDismiss);
474}
475
476// Overridden
477- (void) mouseDown: (NSEvent*)theEvent
478{
479  BOOL buttonClicked;
480  // buttonClicked is set to the value NO when the click occurs in the text cell
481  // and to the value YES when it occurs in the button cell.
482
483  buttonClicked = [_cell trackMouse: theEvent inRect: [self bounds]
484    ofView: self untilMouseUp: YES];
485
486  if (!buttonClicked)
487    [super mouseDown: theEvent];
488}
489
490- (BOOL) textView: (NSTextView *)textView doCommandBySelector: (SEL)command
491{
492  if ([super textView: textView doCommandBySelector: command])
493    return YES;
494  if (sel_isEqual(command, @selector(moveDown:)))
495    {
496      [_cell _performClickWithFrame: [self bounds] inView: self];
497      return YES;
498    }
499  return NO;
500}
501
502- (void) setFrame: (NSRect)frame
503{
504  NSRect rect = NSMakeRect(frame.origin.x, frame.origin.y, frame.size.width, 21);
505  // FIX ME: We shouldn't harcode the height value
506
507  [super setFrame: rect];
508}
509
510@end
511