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