1/* 2 <title>NSToolbar.m</title> 3 4 <abstract>The toolbar class.</abstract> 5 6 Copyright (C) 2002 Free Software Foundation, Inc. 7 8 Author: Gregory John Casamento <greg_casamento@yahoo.com>, 9 Fabien Vallon <fabien.vallon@fr.alcove.com>, 10 Quentin Mathe <qmathe@club-internet.fr> 11 Date: May 2002 12 13 This file is part of the GNUstep GUI Library. 14 15 This library is free software; you can redistribute it and/or 16 modify it under the terms of the GNU Lesser General Public 17 License as published by the Free Software Foundation; either 18 version 2 of the License, or (at your option) any later version. 19 20 This library is distributed in the hope that it will be useful, 21 but WITHOUT ANY WARRANTY; without even the implied warranty of 22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 23 Lesser General Public License for more details. 24 25 You should have received a copy of the GNU Lesser General Public 26 License along with this library; see the file COPYING.LIB. 27 If not, see <http://www.gnu.org/licenses/> or write to the 28 Free Software Foundation, 51 Franklin Street, Fifth Floor, 29 Boston, MA 02110-1301, USA. 30*/ 31 32#import <Foundation/NSObject.h> 33#import <Foundation/NSArray.h> 34#import <Foundation/NSDictionary.h> 35#import <Foundation/NSEnumerator.h> 36#import <Foundation/NSException.h> 37#import <Foundation/NSKeyValueCoding.h> 38#import <Foundation/NSNotification.h> 39#import <Foundation/NSNull.h> 40#import <Foundation/NSRunLoop.h> 41#import <Foundation/NSString.h> 42#import <Foundation/NSTimer.h> 43#import <Foundation/NSUserDefaults.h> 44#import <Foundation/NSDecimalNumber.h> 45#import "AppKit/NSApplication.h" 46#import "AppKit/NSEvent.h" 47#import "AppKit/NSGraphics.h" 48#import "AppKit/NSMenu.h" 49#import "AppKit/NSNibLoading.h" 50#import "AppKit/NSToolbar.h" 51#import "AppKit/NSToolbarItem.h" 52#import "AppKit/NSView.h" 53#import "AppKit/NSWindow.h" 54#import "GNUstepGUI/GSToolbarView.h" 55 56#import "NSToolbarFrameworkPrivate.h" 57#import "GSToolbarCustomizationPalette.h" 58 59// internal 60static NSNotificationCenter *nc = nil; 61static NSMutableArray *toolbars = nil; 62static const int current_version = 1; 63 64// Validation stuff 65static const unsigned int ValidationInterval = 4; 66@class GSValidationCenter; // Mandatory because the interface is declared later 67static GSValidationCenter *vc = nil; 68 69// Extensions 70@interface NSArray (ObjectsWithValueForKey) 71- (NSArray *) objectsWithValue: (id)value forKey: (NSString *)key; 72@end 73 74@implementation NSArray (ObjectsWithValueForKey) 75 76- (NSArray *) objectsWithValue: (id)value forKey: (NSString *)key 77{ 78 NSMutableArray *result = [NSMutableArray array]; 79 NSArray *keys = [self valueForKey: key]; 80 int i, n = 0; 81 82 if (keys == nil) 83 return nil; 84 85 n = [keys count]; 86 87 for (i = 0; i < n; i++) 88 { 89 if ([[keys objectAtIndex: i] isEqual: value]) 90 { 91 [result addObject: [self objectAtIndex: i]]; 92 } 93 } 94 95 if ([result count] == 0) 96 return nil; 97 98 return result; 99} 100@end 101 102/* 103 * Validation support 104 * 105 * Validation support is architectured around a shared validation center, which 106 * is our public interface to handle the validation, behind the scene each 107 * window has an associated validation object created when an observer is added 108 * to the validation center. 109 * A validation object calls the _validate: method on the observer when the 110 * mouse is inside the observed window and only in the case this window is 111 * updated or in the case the mouse stays inside more than four seconds, then 112 * the action will be reiterated every four seconds until the mouse exits. 113 * A validation object owns a window to observe, a tracking rect attached to 114 * the window root view to know when the mouse is inside, a timer to be able to 115 * send the _validate: message periodically, and one ore more observers, then it 116 * is necessary to supply with each registered observer an associated window to 117 * observe. 118 * In the case, an object would observe several windows, the _validate: has a 119 * parameter observedWindow to let us know where the message is coming from. 120 * Because we cannot know surely when a validation object is deallocated, a 121 * method named clean has been added which permits to invalidate a validation 122 * object which must not be used anymore, not calling it would let segmentation 123 * faults occurs. 124 */ 125 126@interface GSValidationObject : NSObject 127{ 128 NSWindow *_window; 129 NSView *_trackingRectView; 130 NSTrackingRectTag _trackingRect; 131 NSMutableArray *_observers; 132 NSTimer *_validationTimer; 133 BOOL _inside; 134 BOOL _validating; 135} 136 137- (NSMutableArray *) observers; 138- (void) setObservers: (NSMutableArray *)observers; 139- (NSWindow *) window; 140- (void) setWindow: (NSWindow *)window; 141- (void) validate; 142- (void) scheduledValidate; 143- (void) clean; 144 145@end 146 147@interface GSValidationCenter : NSObject 148{ 149 NSMutableArray *_vobjs; 150} 151 152+ (GSValidationCenter *) sharedValidationCenter; 153 154- (NSArray *) observersWindow: (NSWindow *)window; 155- (void) addObserver: (id)observer window: (NSWindow *)window; 156- (void) removeObserver: (id)observer window: (NSWindow *)window; 157 158@end 159 160// Validation mechanism 161 162@interface NSWindow (GNUstepPrivate) 163- (NSView *) _windowView; 164@end 165 166@implementation GSValidationObject 167 168- (id) initWithWindow: (NSWindow *)window 169{ 170 if ((self = [super init]) != nil) 171 { 172 _observers = [[NSMutableArray alloc] init]; 173 174 [nc addObserver: self selector: @selector(windowDidUpdate:) 175 name: NSWindowDidUpdateNotification 176 object: window]; 177 [nc addObserver: vc 178 selector: @selector(windowWillClose:) 179 name: NSWindowWillCloseNotification 180 object: window]; 181 182 _trackingRectView = [window _windowView]; 183 _trackingRect 184 = [_trackingRectView addTrackingRect: [_trackingRectView bounds] 185 owner: self 186 userData: nil 187 assumeInside: NO]; 188 _window = window; 189 } 190 return self; 191} 192 193- (void) dealloc 194{ 195 // NSLog(@"vobj dealloc"); 196 197 // [_trackingRectView removeTrackingRect: _trackingRect]; 198 // Not here because the tracking rect retains us, then when the tracking rect 199 // would be deallocated that would create a loop and a segmentation fault. 200 // See next method. 201 202 RELEASE(_observers); 203 204 [super dealloc]; 205} 206 207- (void) clean 208{ 209 if ([_validationTimer isValid]) 210 { 211 [_validationTimer invalidate]; 212 _validationTimer = nil; 213 } 214 215 [nc removeObserver: vc 216 name: NSWindowWillCloseNotification 217 object: _window]; 218 [nc removeObserver: self 219 name: NSWindowDidUpdateNotification 220 object: _window]; 221 222 [self setWindow: nil]; 223 // Needed because the validation timer can retain us and by this way retain also the toolbar which is 224 // currently observing. 225 226 [self setObservers: nil]; // To release observers 227 228 [_trackingRectView removeTrackingRect: _trackingRect]; 229 // We can safely remove the tracking rect here, because it will never call 230 // this method unlike dealloc. 231} 232 233/* 234 * FIXME: Replace the deprecated method which follows by this one when -base 235 * NSObject will implement it. 236 * 237- (id) valueForUndefinedKey: (NSString *)key 238{ 239 if ([key isEqualToString: @"window"] || [key isEqualToString: @"_window"]) 240 return nil; 241 242 return [super valueForUndefinedKey: key]; 243} 244 */ 245 246- (id) handleQueryWithUnboundKey: (NSString *)key 247{ 248 if ([key isEqualToString: @"window"] || [key isEqualToString: @"_window"]) 249 return [NSNull null]; 250 251 return [super handleQueryWithUnboundKey: key]; 252} 253 254- (NSMutableArray *) observers 255{ 256 return _observers; 257} 258 259- (void) setObservers: (NSMutableArray *)observers 260{ 261 ASSIGN(_observers, observers); 262} 263 264- (NSWindow *) window 265{ 266 return _window; 267} 268 269- (void) setWindow: (NSWindow *)window 270{ 271 _window = window; 272} 273 274- (void) validate 275{ 276 if (_validating == NO) 277 { 278 _validating = YES; 279 NS_DURING 280 { 281 [_observers makeObjectsPerformSelector: @selector(_validate:) 282 withObject: _window]; 283 _validating = NO; 284 } 285 NS_HANDLER 286 { 287 _validating = NO; 288 NSLog(@"Problem validating toolbar: %@", localException); 289 } 290 NS_ENDHANDLER 291 } 292} 293 294- (void) mouseEntered: (NSEvent *)event 295{ 296 _inside = YES; 297 [self scheduledValidate]; 298} 299 300- (void) mouseExited: (NSEvent *)event 301{ 302 _inside = NO; 303 if ([_validationTimer isValid]) 304 { 305 [_validationTimer invalidate]; 306 _validationTimer = nil; 307 } 308} 309 310- (void) windowDidUpdate: (NSNotification *)notification 311{ 312 // Validate the toolbar for each update of the window. 313 [self validate]; 314} 315 316- (void) scheduledValidate 317{ 318 if (!_inside) 319 return; 320 321 [self validate]; 322 323 _validationTimer = 324 [NSTimer timerWithTimeInterval: ValidationInterval 325 target: self 326 selector: @selector(scheduledValidate) 327 userInfo: nil 328 repeats: NO]; 329 [[NSRunLoop currentRunLoop] addTimer: _validationTimer 330 forMode: NSDefaultRunLoopMode]; 331} 332 333@end 334 335 336@implementation GSValidationCenter 337 338+ (GSValidationCenter *) sharedValidationCenter 339{ 340 if (vc == nil) 341 { 342 if ((vc = [[GSValidationCenter alloc] init]) != nil) 343 { 344 // Nothing special 345 } 346 } 347 348 return vc; 349} 350 351- (id) init 352{ 353 if ((self = [super init]) != nil) 354 { 355 _vobjs = [[NSMutableArray alloc] init]; 356 } 357 358 return self; 359} 360 361- (void) dealloc 362{ 363 [nc removeObserver: self]; 364 365 RELEASE(_vobjs); 366 367 [super dealloc]; 368} 369 370- (GSValidationObject *) validationObjectForWindow: (NSWindow*)w 371{ 372 return [[_vobjs objectsWithValue: w forKey: @"_window"] objectAtIndex: 0]; 373} 374 375- (NSArray *) observersWindow: (NSWindow *)window 376{ 377 int i; 378 NSArray *observersArray; 379 NSMutableArray *result; 380 381 if (window == nil) 382 { 383 result = [NSMutableArray array]; 384 observersArray = [_vobjs valueForKey: @"_observers"]; 385 for (i = 0; i < [observersArray count]; i++) 386 { 387 [result addObjectsFromArray: [observersArray objectAtIndex: i]]; 388 } 389 return result; 390 } 391 else 392 { 393 return [[self validationObjectForWindow: window] observers]; 394 } 395} 396 397- (void) addObserver: (id)observer window: (NSWindow *)window 398{ 399 GSValidationObject *vobj = [self validationObjectForWindow: window]; 400 NSMutableArray *observersWindow = nil; 401 402 if (window == nil) 403 return; 404 405 if (vobj != nil) 406 { 407 observersWindow = [vobj observers]; 408 } 409 else 410 { 411 vobj = [[GSValidationObject alloc] initWithWindow: window]; 412 [_vobjs addObject: vobj]; 413 RELEASE(vobj); 414 415 observersWindow = [NSMutableArray array]; 416 [vobj setObservers: observersWindow]; 417 } 418 419 [observersWindow addObject: observer]; 420} 421 422- (void) removeObserver: (id)observer window: (NSWindow *)window 423{ 424 GSValidationObject *vobj; 425 NSMutableArray *observersWindow; 426 NSArray *windows; 427 NSEnumerator *e; 428 NSWindow *w; 429 430 if (window == nil) 431 { 432 windows = [_vobjs valueForKey: @"_window"]; 433 } 434 else 435 { 436 windows = [NSArray arrayWithObject: window]; 437 } 438 439 e = [windows objectEnumerator]; 440 441 while ((w = [e nextObject]) != nil) 442 { 443 vobj = [self validationObjectForWindow: w]; 444 observersWindow = [vobj observers]; 445 446 if (observersWindow != nil && [observersWindow containsObject: observer]) 447 { 448 [observersWindow removeObject: observer]; 449 if ([observersWindow count] == 0) 450 { 451 [vobj clean]; 452 [_vobjs removeObjectIdenticalTo: vobj]; 453 } 454 } 455 } 456 457} 458 459- (void) windowWillClose: (NSNotification *)notification 460{ 461 GSValidationObject *vobj; 462 463 // NSLog(@"Window will close"); 464 465 vobj = [self validationObjectForWindow: [notification object]]; 466 if (vobj != nil) 467 { 468 [vobj clean]; 469 [_vobjs removeObjectIdenticalTo: vobj]; 470 } 471} 472 473@end 474 475// --- 476 477@implementation NSToolbar 478 479// Class methods 480 481// Initialize the class when it is loaded 482+ (void) initialize 483{ 484 if (self == [NSToolbar class]) 485 { 486 [self setVersion: current_version]; 487 nc = [NSNotificationCenter defaultCenter]; 488 vc = [GSValidationCenter sharedValidationCenter]; 489 toolbars = [[NSMutableArray alloc] init]; 490 } 491} 492 493// Private class method to access static variable toolbars in subclasses 494 495+ (NSArray *) _toolbarsWithIdentifier: (NSString *)identifier 496{ 497 return [toolbars objectsWithValue: identifier 498 forKey: @"_identifier"]; 499} 500 501// Instance methods 502 503- (id) initWithIdentifier: (NSString *)identifier 504{ 505 NSToolbar *toolbarModel = nil; 506 507 if ((self = [super init]) == nil) 508 return nil; 509 510 ASSIGN(_identifier, identifier); 511 512 _items = [[NSMutableArray alloc] init]; 513 514 // Only set when loaded from a nib 515 _interfaceBuilderItemsByIdentifier = nil; 516 _interfaceBuilderAllowedItemIdentifiers = nil; 517 _interfaceBuilderDefaultItemIdentifiers = nil; 518 _interfaceBuilderSelectableItemIdentifiers = nil; 519 520 toolbarModel = [self _toolbarModel]; 521 522 if (toolbarModel != nil) 523 { 524 _customizationPaletteIsRunning = NO; 525 _allowsUserCustomization = [toolbarModel allowsUserCustomization]; 526 _autosavesConfiguration = [toolbarModel autosavesConfiguration]; 527 ASSIGN(_configurationDictionary, [toolbarModel configurationDictionary]); 528 _displayMode = [toolbarModel displayMode]; 529 _sizeMode = [toolbarModel sizeMode]; 530 _visible = [toolbarModel isVisible]; 531 } 532 else 533 { 534 _customizationPaletteIsRunning = NO; 535 _allowsUserCustomization = NO; 536 _autosavesConfiguration = NO; 537 _configurationDictionary = nil; 538 _displayMode = NSToolbarDisplayModeIconAndLabel; 539 _sizeMode = NSToolbarSizeModeRegular; 540 _visible = YES; 541 } 542 543 _delegate = nil; 544 [self setShowsBaselineSeparator: YES]; 545 546 // Store in list of toolbars 547 [toolbars addObject: self]; 548 549 return self; 550} 551 552- (NSArray *) _identifiersForItems: (NSArray*)items 553{ 554 NSMutableArray *result = [NSMutableArray arrayWithCapacity: [items count]]; 555 NSEnumerator *e = [items objectEnumerator]; 556 NSToolbarItem *item; 557 558 if (items == nil) 559 return nil; 560 561 while ((item = [e nextObject]) != nil) 562 { 563 [result addObject: [item itemIdentifier]]; 564 } 565 return result; 566} 567 568- (void) encodeWithCoder: (NSCoder*)aCoder 569{ 570 if ([aCoder allowsKeyedCoding]) 571 { 572 // FIXME 573 } 574 else 575 { 576 // FIXME 577 } 578} 579 580- (id) initWithCoder: (NSCoder *)aCoder 581{ 582 if ([aCoder allowsKeyedCoding]) 583 { 584 ASSIGN(_identifier, [aCoder decodeObjectForKey:@"NSToolbarIdentifier"]); 585 _items = [[NSMutableArray alloc] init]; 586 587 ASSIGN(_interfaceBuilderItemsByIdentifier, [aCoder decodeObjectForKey: @"NSToolbarIBIdentifiedItems"]); 588 ASSIGN(_interfaceBuilderAllowedItemIdentifiers, [self _identifiersForItems: [aCoder decodeObjectForKey: @"NSToolbarIBAllowedItems"]]); 589 ASSIGN(_interfaceBuilderDefaultItemIdentifiers, [self _identifiersForItems: [aCoder decodeObjectForKey: @"NSToolbarIBDefaultItems"]]); 590 ASSIGN(_interfaceBuilderSelectableItemIdentifiers, [self _identifiersForItems: [aCoder decodeObjectForKey: @"NSToolbarIBSelectableItems"]]); 591 592 _customizationPaletteIsRunning = NO; 593 _configurationDictionary = nil; 594 595 [self setAllowsUserCustomization: [aCoder decodeBoolForKey: @"NSToolbarAllowsUserCustomization"]]; 596 [self setAutosavesConfiguration: [aCoder decodeBoolForKey: @"NSToolbarAutosavesConfiguration"]]; 597 [self setDisplayMode: [aCoder decodeIntForKey: @"NSToolbarDisplayMode"]]; 598 [self setShowsBaselineSeparator: [aCoder decodeBoolForKey: @"NSToolbarShowsBaselineSeparator"]]; 599 [self setSizeMode: [aCoder decodeIntForKey: @"NSToolbarSizeMode"]]; 600 [self setVisible: [aCoder decodeBoolForKey: @"NSToolbarPrefersToBeShown"]]; 601 [self setDelegate: [aCoder decodeObjectForKey: @"NSToolbarDelegate"]]; 602 } 603 else 604 { 605 // FIXME 606 } 607 608 [self _build]; 609 610 // Store in list of toolbars 611 [toolbars addObject: self]; 612 613 return self; 614} 615 616- (void) dealloc 617{ 618 //NSLog(@"Toolbar dealloc %@", self); 619 [self _setToolbarView: nil]; 620 621 // Use DESTROY ? 622 RELEASE(_identifier); 623 RELEASE(_selectedItemIdentifier); 624 RELEASE(_configurationDictionary); 625 RELEASE(_items); 626 627 DESTROY(_interfaceBuilderItemsByIdentifier); 628 DESTROY(_interfaceBuilderAllowedItemIdentifiers); 629 DESTROY(_interfaceBuilderDefaultItemIdentifiers); 630 DESTROY(_interfaceBuilderSelectableItemIdentifiers); 631 632 if (_delegate != nil) 633 { 634 [nc removeObserver: _delegate name: nil object: self]; 635 _delegate = nil; 636 } 637 638 [super dealloc]; 639} 640 641// FIXME: Hack 642- (oneway void) release 643{ 644 // When a toolbar has no external references any more, it's necessary 645 // to remove the toolbar from the master list, so that it 646 // doesn't cause a memory leak. 647 if ([self retainCount] == 2) 648 [toolbars removeObjectIdenticalTo: self]; 649 650 [super release]; 651} 652 653- (id) valueForUndefinedKey: (NSString *)key 654{ 655 if ([key isEqualToString: @"window"] || [key isEqualToString: @"_window"]) 656 return nil; 657 658 return [super valueForUndefinedKey: key]; 659} 660 661- (void) insertItemWithItemIdentifier: (NSString *)itemIdentifier 662 atIndex: (NSInteger)index 663{ 664 [self _insertItemWithItemIdentifier: itemIdentifier 665 atIndex: index 666 broadcast: YES]; 667} 668 669- (void) removeItemAtIndex: (NSInteger)index 670{ 671 [self _removeItemAtIndex: index broadcast: YES]; 672} 673 674- (void) runCustomizationPalette: (id)sender 675{ 676 GSToolbarCustomizationPalette *palette; 677 678 if (![self allowsUserCustomization]) 679 { 680 return; 681 } 682 if (_customizationPaletteIsRunning) 683 { 684 NSLog(@"Customization palette is already running for toolbar: %@", self); 685 return; 686 } 687 688 if (!_visible) 689 { 690 [self setVisible:YES]; 691 } 692 693 palette = [GSToolbarCustomizationPalette palette]; 694 695 if (palette != nil) 696 _customizationPaletteIsRunning = YES; 697 698 [palette showForToolbar: self]; 699} 700 701- (void) validateVisibleItems 702{ 703 NSEnumerator *e = [[self visibleItems] objectEnumerator]; 704 NSToolbarItem *item = nil; 705 706 while ((item = [e nextObject]) != nil) 707 { 708 [item validate]; 709 } 710} 711 712// Accessors 713 714- (BOOL) allowsUserCustomization 715{ 716 return _allowsUserCustomization; 717} 718 719- (BOOL) autosavesConfiguration 720{ 721 return _autosavesConfiguration; 722} 723 724- (NSDictionary *) configurationDictionary 725{ 726 return _configurationDictionary; 727} 728 729- (BOOL) customizationPaletteIsRunning 730{ 731 return _customizationPaletteIsRunning; 732} 733 734- (void) _setCustomizationPaletteIsRunning: (BOOL)isRunning 735{ 736 _customizationPaletteIsRunning = isRunning; 737} 738 739- (id) delegate 740{ 741 return _delegate; 742} 743 744- (NSToolbarDisplayMode) displayMode 745{ 746 return _displayMode; 747} 748 749- (NSString *) identifier 750{ 751 return _identifier; 752} 753 754- (NSArray *) items 755{ 756 return _items; 757} 758 759- (NSString *) selectedItemIdentifier 760{ 761 return _selectedItemIdentifier; 762} 763 764- (BOOL) showsBaselineSeparator 765{ 766 return _showsBaselineSeparator; 767} 768 769- (NSArray *) visibleItems 770{ 771 if ([_toolbarView superview] == nil) 772 { 773 return nil; 774 } 775 else 776 { 777 return [[_toolbarView _visibleBackViews] valueForKey: @"toolbarItem"]; 778 } 779} 780 781- (void) setAllowsUserCustomization: (BOOL)flag 782{ 783 [self _setAllowsUserCustomization: flag broadcast: YES]; 784} 785 786- (void) setAutosavesConfiguration: (BOOL)flag 787{ 788 [self _setAutosavesConfiguration: flag broadcast: YES]; 789} 790 791- (void) setConfigurationFromDictionary: (NSDictionary *)configDict 792{ 793 int i = 0; 794 id item = nil; 795 NSArray *items = nil; 796 NSEnumerator *en = nil; 797 798 ASSIGN(_configurationDictionary, configDict); 799 800 // set 801 _visible = [[_configurationDictionary objectForKey: @"isVisible"] boolValue]; 802 _displayMode = [[_configurationDictionary objectForKey: @"displayMode"] intValue]; 803 804 // remove all items... 805 for(i = 0; i < [_items count]; i++) 806 { 807 [self _removeItemAtIndex: 0 broadcast: YES]; 808 } 809 810 // add all of the items... 811 i = 0; 812 items = [_configurationDictionary objectForKey: @"items"]; 813 en = [items objectEnumerator]; 814 while((item = [en nextObject]) != nil) 815 { 816 [self _insertItemWithItemIdentifier: item 817 atIndex: i++ 818 broadcast: YES]; 819 } 820} 821 822/** 823 * Sets the receivers delegate ... this is the object which will receive 824 * -toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar: 825 * -toolbarAllowedItemIdentifiers: and -toolbarDefaultItemIdentifiers: 826 * messages. 827 */ 828- (void) setDelegate: (id)delegate 829{ 830 if (_delegate == delegate) 831 return; 832 833 if (_delegate != nil) 834 [nc removeObserver: _delegate name: nil object: self]; 835 836 // Assign the delegate... 837 _delegate = delegate; 838 839 if (_delegate != nil) 840 { 841 #define CHECK_REQUIRED_METHOD(selector_name) \ 842 if (![_delegate respondsToSelector: @selector(selector_name)]) \ 843 [NSException raise: NSInternalInconsistencyException \ 844 format: @"delegate does not respond to %@",@#selector_name] 845 846 if (_interfaceBuilderItemsByIdentifier == nil) 847 { 848 CHECK_REQUIRED_METHOD(toolbar:itemForItemIdentifier: 849 willBeInsertedIntoToolbar:); 850 } 851 if (_interfaceBuilderAllowedItemIdentifiers == nil) 852 { 853 CHECK_REQUIRED_METHOD(toolbarAllowedItemIdentifiers:); 854 } 855 if (_interfaceBuilderDefaultItemIdentifiers == nil) 856 { 857 CHECK_REQUIRED_METHOD(toolbarDefaultItemIdentifiers:); 858 } 859 860 #define SET_DELEGATE_NOTIFICATION(notif_name) \ 861 if ([_delegate respondsToSelector: @selector(toolbar##notif_name:)]) \ 862 [nc addObserver: _delegate \ 863 selector: @selector(toolbar##notif_name:) \ 864 name: NSToolbar##notif_name##Notification object: self] 865 866 SET_DELEGATE_NOTIFICATION(DidRemoveItem); 867 SET_DELEGATE_NOTIFICATION(WillAddItem); 868 } 869 870 [self _build]; 871} 872 873- (NSArray *) _selectableItemIdentifiers 874{ 875 NSArray *selectableIdentifiers = nil; 876 877 if (_delegate != nil && 878 [_delegate respondsToSelector: @selector(toolbarSelectableItemIdentifiers:)]) 879 { 880 selectableIdentifiers = [_delegate toolbarSelectableItemIdentifiers: self]; 881 if (selectableIdentifiers == nil) 882 { 883 NSLog(@"Toolbar delegate returns no such selectable item identifiers"); 884 } 885 } 886 887 if (selectableIdentifiers == nil) 888 { 889 selectableIdentifiers = _interfaceBuilderSelectableItemIdentifiers; 890 } 891 892 return selectableIdentifiers; 893} 894 895- (void) setSelectedItemIdentifier: (NSString *)identifier 896{ 897 NSArray *selectedItems; 898 NSArray *itemsToSelect; 899 NSEnumerator *e; 900 NSToolbarItem *item; 901 NSArray *selectableIdentifiers; 902 BOOL updated = NO; 903 904 // First, we have to deselect the previous selected toolbar items 905 selectedItems = [[self items] objectsWithValue: [self selectedItemIdentifier] 906 forKey: @"_itemIdentifier"]; 907 e = [selectedItems objectEnumerator]; 908 while ((item = [e nextObject]) != nil) 909 { 910 [item _setSelected: NO]; 911 } 912 913 selectableIdentifiers = [self _selectableItemIdentifiers]; 914 915 if (selectableIdentifiers == nil) 916 return; 917 918 itemsToSelect = [_items objectsWithValue: identifier 919 forKey: @"_itemIdentifier"]; 920 e = [itemsToSelect objectEnumerator]; 921 while ((item = [e nextObject]) != nil) 922 { 923 if ([selectableIdentifiers containsObject: [item itemIdentifier]]) 924 { 925 if (![item _selected]) 926 [item _setSelected: YES]; 927 updated = YES; 928 } 929 } 930 931 if (updated) 932 { 933 ASSIGN(_selectedItemIdentifier, identifier); 934 } 935 else 936 { 937 NSLog(@"Toolbar delegate returns no such selectable item identifiers"); 938 } 939} 940 941- (NSToolbarSizeMode) sizeMode 942{ 943 return _sizeMode; 944} 945 946- (BOOL) isVisible 947{ 948 return _visible; 949} 950 951 952/** 953 * Sets the receivers delegate ... this is the object which will receive 954 * -toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar: 955 * -toolbarAllowedItemIdentifiers: and -toolbarDefaultItemIdentifiers: 956 * messages. 957 */ 958 959- (void) setDisplayMode: (NSToolbarDisplayMode)displayMode 960{ 961 [self _setDisplayMode: displayMode broadcast: YES]; 962} 963 964- (void) setSizeMode: (NSToolbarSizeMode)sizeMode 965{ 966 [self _setSizeMode: sizeMode broadcast: YES]; 967} 968 969- (void) setVisible: (BOOL)shown 970{ 971 [self _setVisible: shown broadcast: NO]; 972} 973 974- (void) setShowsBaselineSeparator: (BOOL)flag 975{ 976 _showsBaselineSeparator = flag; 977 978 if (_showsBaselineSeparator) 979 [_toolbarView setBorderMask: GSToolbarViewBottomBorder]; 980 else 981 [_toolbarView setBorderMask: 0]; 982} 983 984// Private methods 985 986- (NSArray *) _defaultItemIdentifiers 987{ 988 if (_delegate != nil) 989 { 990 return [_delegate toolbarDefaultItemIdentifiers:self]; 991 } 992 else 993 { 994 return _interfaceBuilderDefaultItemIdentifiers; 995 } 996} 997 998/* 999 * Toolbar build : 1000 * will use the delegate when there is no toolbar model 1001 */ 1002- (void) _build 1003{ 1004 NSToolbar *toolbarModel; 1005 NSArray *wantedItemIdentifiers; 1006 NSEnumerator *e; 1007 id itemIdentifier; 1008 int i = 0; 1009 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 1010 NSString *tableKey = 1011 [NSString stringWithFormat: @"NSToolbar Config %@",_identifier]; 1012 NSDictionary *config = [defaults objectForKey: tableKey]; 1013 1014 if (config) 1015 { 1016 NSToolbarDisplayMode displayMode = 0; 1017 NSToolbarSizeMode sizeMode = 0; 1018 1019 displayMode = (NSToolbarDisplayMode)[[config objectForKey: @"displayMode"] intValue]; 1020 [self setDisplayMode: displayMode]; 1021 sizeMode = (NSToolbarSizeMode)[[config objectForKey: @"sizeMode"] intValue]; 1022 [self setSizeMode: sizeMode]; 1023 } 1024 1025 // Switch off toolbar view reload 1026 _build = YES; 1027 1028 RELEASE(_items); 1029 _items = [[NSMutableArray alloc] init]; 1030 1031 toolbarModel = [self _toolbarModel]; 1032 1033 wantedItemIdentifiers = [self _itemsFromConfig]; 1034 if(wantedItemIdentifiers == nil) 1035 { 1036 if (toolbarModel != nil) 1037 { 1038 wantedItemIdentifiers = 1039 [[toolbarModel items] valueForKey: @"_itemIdentifier"]; 1040 } 1041 else 1042 { 1043 wantedItemIdentifiers = [self _defaultItemIdentifiers]; 1044 } 1045 } 1046 if (wantedItemIdentifiers == nil) 1047 { 1048 _build = NO; 1049 return; 1050 } 1051 1052 e = [wantedItemIdentifiers objectEnumerator]; 1053 while ((itemIdentifier = [e nextObject]) != nil) 1054 { 1055 [self _insertItemWithItemIdentifier: itemIdentifier 1056 atIndex: [_items count] 1057 broadcast: NO]; 1058 i++; 1059 } 1060 1061 _build = NO; 1062 // Now do the toolbar view reload 1063 if (_toolbarView != nil) 1064 [_toolbarView _reload]; 1065} 1066 1067- (void) _resetConfig 1068{ 1069 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 1070 NSString *tableKey = 1071 [NSString stringWithFormat: @"NSToolbar Config %@",_identifier]; 1072 [defaults removeObjectForKey: tableKey]; 1073 [self _build]; 1074} 1075 1076- (BOOL) usesStandardBackgroundColor 1077{ 1078 return [_toolbarView _usesStandardBackgroundColor]; 1079} 1080 1081- (void) setUsesStandardBackgroundColor: (BOOL)standard 1082{ 1083 [_toolbarView _setUsesStandardBackgroundColor: standard]; 1084} 1085 1086- (int) _indexOfItem: (NSToolbarItem *)item 1087{ 1088 return [_items indexOfObjectIdenticalTo: item]; 1089} 1090 1091- (void) _insertPassivelyItem: (NSToolbarItem *)item atIndex: (int)newIndex 1092{ 1093 if (![_items containsObject: item]) 1094 { 1095 if (newIndex > [_items count] - 1) 1096 { 1097 newIndex = [_items count] - 1; 1098 } 1099 else if(newIndex < 0) 1100 { 1101 newIndex = 0; 1102 } 1103 1104 [_items insertObject: item atIndex: newIndex]; 1105 } 1106 else 1107 { 1108 NSLog(@"Error: the toolbar already contains the item to insert."); 1109 } 1110} 1111 1112- (void) _saveConfig 1113{ 1114 if (_identifier != nil) 1115 { 1116 NSUserDefaults *defaults; 1117 NSString *tableKey; 1118 id config; 1119 NSMutableArray *items = [NSMutableArray array]; 1120 id item; 1121 NSEnumerator *en = [_items objectEnumerator]; 1122 1123 defaults = [NSUserDefaults standardUserDefaults]; 1124 tableKey = 1125 [NSString stringWithFormat: @"NSToolbar Config %@",_identifier]; 1126 1127 config = [defaults objectForKey: tableKey]; 1128 1129 if (config == nil) 1130 { 1131 config = [NSMutableDictionary dictionary]; 1132 } 1133 else 1134 { 1135 config = [config mutableCopy]; 1136 } 1137 1138 [config setObject: [NSNumber numberWithBool: _visible] forKey: @"isVisible"]; 1139 [config setObject: [NSNumber numberWithInt: _displayMode] forKey: @"displayMode"]; 1140 [config setObject: [NSNumber numberWithInt: _sizeMode] forKey: @"sizeMode"]; 1141 while((item = [en nextObject]) != nil) 1142 { 1143 [items addObject: [item itemIdentifier]]; 1144 } 1145 [config setObject: items forKey: @"items"]; 1146 1147 // write to defaults 1148 [defaults setObject: config forKey: tableKey]; 1149 ASSIGN(_configurationDictionary, config); 1150 } 1151} 1152 1153- (BOOL) _containsItemWithIdentifier: (NSString *)identifier 1154{ 1155 NSEnumerator *en = [_items objectEnumerator]; 1156 id item = nil; 1157 1158 while((item = [en nextObject]) != nil) 1159 { 1160 if([identifier isEqual: [item itemIdentifier]]) 1161 { 1162 return YES; 1163 } 1164 } 1165 return NO; 1166} 1167 1168- (NSArray *) _itemsFromConfig 1169{ 1170 NSArray *items = nil; 1171 if (_identifier != nil) 1172 { 1173 NSUserDefaults *defaults; 1174 NSString *tableKey; 1175 id config; 1176 1177 defaults = [NSUserDefaults standardUserDefaults]; 1178 tableKey = 1179 [NSString stringWithFormat: @"NSToolbar Config %@",_identifier]; 1180 1181 config = [defaults objectForKey: tableKey]; 1182 1183 if (config != nil) 1184 { 1185 items = [config objectForKey: @"items"]; 1186 } 1187 } 1188 return items; 1189} 1190 1191- (NSToolbar *) _toolbarModel 1192{ 1193 NSArray *linked; 1194 id toolbar; 1195 1196 linked = [[self class] _toolbarsWithIdentifier: [self identifier]]; 1197 1198 if (linked != nil && [linked count] > 0) 1199 { 1200 toolbar = [linked objectAtIndex: 0]; 1201 1202 // Toolbar model class must be identical to self class : 1203 // an NSToolbar instance cannot use a NSToolbar instance as a model 1204 if ([toolbar isMemberOfClass: [self class]] && toolbar != self) 1205 return toolbar; 1206 else 1207 return nil; 1208 } 1209 1210 return nil; 1211} 1212 1213- (NSToolbarItem *) _toolbarItemForIdentifier: (NSString *)itemIdent willBeInsertedIntoToolbar: (BOOL)insert 1214{ 1215 NSToolbarItem *item = nil; 1216 1217 if ([itemIdent isEqual: NSToolbarSeparatorItemIdentifier] || 1218 [itemIdent isEqual: NSToolbarSpaceItemIdentifier] || 1219 [itemIdent isEqual: NSToolbarFlexibleSpaceItemIdentifier] || 1220 [itemIdent isEqual: NSToolbarShowColorsItemIdentifier] || 1221 [itemIdent isEqual: NSToolbarShowFontsItemIdentifier] || 1222 [itemIdent isEqual: NSToolbarCustomizeToolbarItemIdentifier] || 1223 [itemIdent isEqual: NSToolbarPrintItemIdentifier]) 1224 { 1225 item = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdent] autorelease]; 1226 } 1227 1228 if (item == nil && _delegate != nil) 1229 { 1230 item = [_delegate toolbar: self itemForItemIdentifier: itemIdent 1231 willBeInsertedIntoToolbar: insert]; 1232 } 1233 1234 if (item == nil && _interfaceBuilderItemsByIdentifier) 1235 { 1236 item = [_interfaceBuilderItemsByIdentifier objectForKey: itemIdent]; 1237 } 1238 1239 return item; 1240} 1241 1242 1243/* 1244 * 1245 * The methods below handles the toolbar edition and broadcasts each associated 1246 * event to the other toolbars with identical identifiers. 1247 * Warning : broadcast process only happens between instances based on the same 1248 * class. 1249 */ 1250 1251#define TRANSMIT(signature) \ 1252 NSEnumerator *e = [[NSToolbar _toolbarsWithIdentifier: _identifier] objectEnumerator]; \ 1253 NSToolbar *toolbar; \ 1254 \ 1255 while ((toolbar = [e nextObject]) != nil) \ 1256 { \ 1257 if (toolbar != self && [self isMemberOfClass: [self class]]) \ 1258 [toolbar signature]; \ 1259 } \ 1260 1261- (NSArray *) _allowedItemIdentifiers 1262{ 1263 if (_delegate) 1264 { 1265 return [_delegate toolbarAllowedItemIdentifiers: self]; 1266 } 1267 else 1268 { 1269 return _interfaceBuilderAllowedItemIdentifiers; 1270 } 1271} 1272 1273- (void) _insertItemWithItemIdentifier: (NSString *)itemIdentifier 1274 atIndex: (int)index 1275 broadcast: (BOOL)broadcast 1276{ 1277 NSToolbarItem *item = nil; 1278 NSArray *allowedItems = [self _allowedItemIdentifiers]; 1279 1280 if ([allowedItems containsObject: itemIdentifier]) 1281 { 1282 item = [self _toolbarItemForIdentifier: itemIdentifier willBeInsertedIntoToolbar: YES]; 1283 1284 if (item != nil) 1285 { 1286 NSArray *selectableItems = [self _selectableItemIdentifiers]; 1287 1288 if ([selectableItems containsObject: itemIdentifier]) 1289 [item _setSelectable: YES]; 1290 1291 [nc postNotificationName: NSToolbarWillAddItemNotification 1292 object: self 1293 userInfo: [NSDictionary dictionaryWithObject: item forKey: @"item"]]; 1294 [item _setToolbar: self]; 1295 [item _layout]; 1296 [_items insertObject: item atIndex: index]; 1297 1298 // We reload the toolbarView each time a new item is added except when 1299 // we build/create the toolbar 1300 if (!_build) 1301 [_toolbarView _reload]; 1302 1303 if (broadcast) 1304 { 1305 TRANSMIT(_insertItemWithItemIdentifier: itemIdentifier 1306 atIndex: index 1307 broadcast: NO); 1308 } 1309 } 1310 } 1311 1312} 1313 1314- (void) _removeItemAtIndex: (int)index broadcast: (BOOL)broadcast 1315{ 1316 id item = [_items objectAtIndex: index]; 1317 1318 RETAIN(item); 1319 [self _performRemoveItem: item]; 1320 [self _concludeRemoveItem: item atIndex: index broadcast: broadcast]; 1321 RELEASE(item); 1322} 1323 1324- (void) _performRemoveItem: (NSToolbarItem *)item 1325{ 1326 [_items removeObject: item]; 1327 [_toolbarView _reload]; 1328 [self _saveConfig]; 1329} 1330 1331- (void) _concludeRemoveItem: (NSToolbarItem *)item atIndex: (int)index broadcast: (BOOL)broadcast 1332{ 1333 [nc postNotificationName: NSToolbarDidRemoveItemNotification 1334 object: self 1335 userInfo: [NSDictionary dictionaryWithObject: item forKey: @"item"]]; 1336 1337 if (broadcast) 1338 { 1339 TRANSMIT(_removeItemAtIndex: index broadcast: NO); 1340 } 1341} 1342 1343- (void) _setAllowsUserCustomization: (BOOL)flag broadcast: (BOOL)broadcast 1344{ 1345 _allowsUserCustomization = flag; 1346 1347 if (broadcast) 1348 { 1349 TRANSMIT(_setAllowsUserCustomization: _allowsUserCustomization 1350 broadcast: NO); 1351 } 1352} 1353 1354- (void) _setAutosavesConfiguration: (BOOL)flag broadcast: (BOOL)broadcast 1355{ 1356 _autosavesConfiguration = flag; 1357 1358 if (broadcast) 1359 { 1360 TRANSMIT(_setAutosavesConfiguration: _autosavesConfiguration 1361 broadcast: NO); 1362 } 1363} 1364 1365- (void) _setConfigurationFromDictionary: (NSDictionary *)configDict 1366 broadcast: (BOOL)broadcast 1367{ 1368 ASSIGN(_configurationDictionary, configDict); 1369 1370 if (broadcast) 1371 { 1372 TRANSMIT(_setConfigurationFromDictionary: _configurationDictionary 1373 broadcast: NO); 1374 } 1375} 1376 1377- (void) _moveItemFromIndex: (int)index toIndex: (int)newIndex broadcast: (BOOL)broadcast 1378{ 1379 id item; 1380 1381 item = RETAIN([_items objectAtIndex: index]); 1382 [_items removeObjectAtIndex: index]; 1383 if (newIndex > [_items count] - 1) 1384 { 1385 [_items addObject: item]; 1386 } 1387 else 1388 { 1389 [_items insertObject: item atIndex: newIndex]; 1390 } 1391 [_toolbarView _reload]; 1392 1393 RELEASE(item); 1394 1395 if (broadcast) 1396 { 1397 TRANSMIT(_moveItemFromIndex: index toIndex: newIndex broadcast: NO); 1398 } 1399} 1400 1401- (void) _setDisplayMode: (NSToolbarDisplayMode)displayMode 1402 broadcast: (BOOL)broadcast 1403{ 1404 if (_displayMode != displayMode) 1405 { 1406 _displayMode = displayMode; 1407 1408 if ([self isVisible]) 1409 { 1410 [_toolbarView _reload]; 1411 [(id)[[_toolbarView window] _windowView] adjustToolbarView: _toolbarView]; 1412 } 1413 1414 if (broadcast) 1415 { 1416 TRANSMIT(_setDisplayMode: _displayMode broadcast: NO); 1417 } 1418 } 1419} 1420 1421- (void) _setSizeMode: (NSToolbarSizeMode)sizeMode 1422 broadcast: (BOOL)broadcast 1423{ 1424 if (_sizeMode != sizeMode) 1425 { 1426 _sizeMode = sizeMode; 1427 1428 if ([self isVisible]) 1429 { 1430 [_toolbarView _reload]; 1431 [(id)[[_toolbarView window] _windowView] adjustToolbarView: _toolbarView]; 1432 } 1433 1434 if (broadcast) 1435 { 1436 TRANSMIT(_setSizeMode: _sizeMode broadcast: NO); 1437 } 1438 } 1439} 1440 1441 1442- (NSWindow*) _window 1443{ 1444 NSWindow *window = [_toolbarView window]; 1445 NSEnumerator *wenum; 1446 1447 if (window) 1448 return window; 1449 1450 wenum = [GSAllWindows() objectEnumerator]; 1451 while ((window = [wenum nextObject])) 1452 { 1453 if ([window toolbar] == self) 1454 return window; 1455 } 1456 1457 return nil; 1458} 1459 1460- (void) _setVisible: (BOOL)shown broadcast: (BOOL)broadcast 1461{ 1462 if (_visible != shown) 1463 { 1464 _visible = shown; 1465 1466 if (shown) 1467 [self _build]; 1468 1469 if (shown) 1470 { 1471 if ((_toolbarView == nil) || ([_toolbarView superview] == nil)) 1472 { 1473 NSWindow *w = [self _window]; 1474 1475 [(id)[w _windowView] addToolbarView: [self _toolbarView]]; 1476 } 1477 } 1478 else 1479 { 1480 if ((_toolbarView != nil) && ([_toolbarView superview] != nil)) 1481 { 1482 NSWindow *w = [self _window]; 1483 1484 [(id)[w _windowView] removeToolbarView: [self _toolbarView]]; 1485 } 1486 } 1487 1488 if (broadcast) 1489 { 1490 TRANSMIT(_setVisible: _visible broadcast: NO); 1491 } 1492 } 1493} 1494 1495// Private Accessors 1496 1497- (void) _setToolbarView: (GSToolbarView *)toolbarView 1498{ 1499 if (_toolbarView == toolbarView) 1500 return; 1501 1502 if (_toolbarView != nil) 1503 { 1504 // We unset the toolbar from the previous toolbar view 1505 [_toolbarView setToolbar: nil]; 1506 [vc removeObserver: self window: nil]; 1507 } 1508 1509 ASSIGN(_toolbarView, toolbarView); 1510 1511 if (toolbarView != nil) 1512 { 1513 // In the case the window parameter is a nil value, nothing happens. 1514 [vc addObserver: self window: [toolbarView window]]; 1515 // We set the toolbar on the new toolbar view 1516 [_toolbarView setToolbar: self]; 1517 } 1518} 1519 1520- (GSToolbarView *) _toolbarView 1521{ 1522 if (_toolbarView == nil) 1523 { 1524 // Instantiate the toolbar view 1525 // addToolbarView: method will set the toolbar view to the right 1526 // frame 1527 GSToolbarView *toolbarView = [[GSToolbarView alloc] 1528 initWithFrame: 1529 NSMakeRect(0, 0, 100, 100)]; 1530 1531 [toolbarView setAutoresizingMask: NSViewWidthSizable | NSViewMinYMargin]; 1532 if (_showsBaselineSeparator) 1533 [toolbarView setBorderMask: GSToolbarViewBottomBorder]; 1534 else 1535 [toolbarView setBorderMask: 0]; 1536 1537 // Load the toolbar view inside the toolbar 1538 _toolbarView = toolbarView; 1539 [_toolbarView setToolbar: self]; 1540 } 1541 1542 return _toolbarView; 1543} 1544 1545- (void) _toolbarViewWillMoveToSuperview: (NSView *)newSuperview 1546{ 1547 // Must synchronize the validation system 1548 // _toolbarView should never be nil here 1549 // We don't handle synchronization when the toolbar view is added to a superview not 1550 // binded to a window, such superview being later moved to a window. (FIX ME ?) 1551 1552 // NSLog(@"Moving to window %@", [newSuperview window]); 1553 1554 [vc removeObserver: self window: nil]; 1555 if (newSuperview != nil) 1556 [vc addObserver: self window: [newSuperview window]]; 1557} 1558 1559- (void) _validate: (NSWindow *)observedWindow 1560{ 1561 // We observe only one window, then we ignore observedWindow. 1562 1563 [self validateVisibleItems]; 1564} 1565 1566 1567 1568@end 1569