/* NSToolbar.m The toolbar class. Copyright (C) 2002 Free Software Foundation, Inc. Author: Gregory John Casamento , Fabien Vallon , Quentin Mathe Date: May 2002 This file is part of the GNUstep GUI Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; see the file COPYING.LIB. If not, see or write to the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #import #import #import #import #import #import #import #import #import #import #import #import #import "AppKit/NSApplication.h" #import "AppKit/NSEvent.h" #import "AppKit/NSGraphics.h" #import "AppKit/NSMenu.h" #import "AppKit/NSNibLoading.h" #import "AppKit/NSToolbar.h" #import "AppKit/NSToolbarItem.h" #import "AppKit/NSView.h" #import "AppKit/NSWindow.h" #import "GNUstepGUI/GSToolbarView.h" #import "NSToolbarFrameworkPrivate.h" #import "GSToolbarCustomizationPalette.h" // internal static NSNotificationCenter *nc = nil; static NSMutableArray *toolbars = nil; static const int current_version = 1; // Validation stuff static const unsigned int ValidationInterval = 4; @class GSValidationCenter; // Mandatory because the interface is declared later static GSValidationCenter *vc = nil; // Extensions @interface NSArray (ObjectsWithValueForKey) - (NSArray *) objectsWithValue: (id)value forKey: (NSString *)key; @end @implementation NSArray (ObjectsWithValueForKey) - (NSArray *) objectsWithValue: (id)value forKey: (NSString *)key { NSMutableArray *result = [NSMutableArray array]; NSArray *keys = [self valueForKey: key]; int i, n = 0; if (keys == nil) return nil; n = [keys count]; for (i = 0; i < n; i++) { if ([[keys objectAtIndex: i] isEqual: value]) { [result addObject: [self objectAtIndex: i]]; } } if ([result count] == 0) return nil; return result; } @end /* * Validation support * * Validation support is architectured around a shared validation center, which * is our public interface to handle the validation, behind the scene each * window has an associated validation object created when an observer is added * to the validation center. * A validation object calls the _validate: method on the observer when the * mouse is inside the observed window and only in the case this window is * updated or in the case the mouse stays inside more than four seconds, then * the action will be reiterated every four seconds until the mouse exits. * A validation object owns a window to observe, a tracking rect attached to * the window root view to know when the mouse is inside, a timer to be able to * send the _validate: message periodically, and one ore more observers, then it * is necessary to supply with each registered observer an associated window to * observe. * In the case, an object would observe several windows, the _validate: has a * parameter observedWindow to let us know where the message is coming from. * Because we cannot know surely when a validation object is deallocated, a * method named clean has been added which permits to invalidate a validation * object which must not be used anymore, not calling it would let segmentation * faults occurs. */ @interface GSValidationObject : NSObject { NSWindow *_window; NSView *_trackingRectView; NSTrackingRectTag _trackingRect; NSMutableArray *_observers; NSTimer *_validationTimer; BOOL _inside; BOOL _validating; } - (NSMutableArray *) observers; - (void) setObservers: (NSMutableArray *)observers; - (NSWindow *) window; - (void) setWindow: (NSWindow *)window; - (void) validate; - (void) scheduledValidate; - (void) clean; @end @interface GSValidationCenter : NSObject { NSMutableArray *_vobjs; } + (GSValidationCenter *) sharedValidationCenter; - (NSArray *) observersWindow: (NSWindow *)window; - (void) addObserver: (id)observer window: (NSWindow *)window; - (void) removeObserver: (id)observer window: (NSWindow *)window; @end // Validation mechanism @interface NSWindow (GNUstepPrivate) - (NSView *) _windowView; @end @implementation GSValidationObject - (id) initWithWindow: (NSWindow *)window { if ((self = [super init]) != nil) { _observers = [[NSMutableArray alloc] init]; [nc addObserver: self selector: @selector(windowDidUpdate:) name: NSWindowDidUpdateNotification object: window]; [nc addObserver: vc selector: @selector(windowWillClose:) name: NSWindowWillCloseNotification object: window]; _trackingRectView = [window _windowView]; _trackingRect = [_trackingRectView addTrackingRect: [_trackingRectView bounds] owner: self userData: nil assumeInside: NO]; _window = window; } return self; } - (void) dealloc { // NSLog(@"vobj dealloc"); // [_trackingRectView removeTrackingRect: _trackingRect]; // Not here because the tracking rect retains us, then when the tracking rect // would be deallocated that would create a loop and a segmentation fault. // See next method. RELEASE(_observers); [super dealloc]; } - (void) clean { if ([_validationTimer isValid]) { [_validationTimer invalidate]; _validationTimer = nil; } [nc removeObserver: vc name: NSWindowWillCloseNotification object: _window]; [nc removeObserver: self name: NSWindowDidUpdateNotification object: _window]; [self setWindow: nil]; // Needed because the validation timer can retain us and by this way retain also the toolbar which is // currently observing. [self setObservers: nil]; // To release observers [_trackingRectView removeTrackingRect: _trackingRect]; // We can safely remove the tracking rect here, because it will never call // this method unlike dealloc. } /* * FIXME: Replace the deprecated method which follows by this one when -base * NSObject will implement it. * - (id) valueForUndefinedKey: (NSString *)key { if ([key isEqualToString: @"window"] || [key isEqualToString: @"_window"]) return nil; return [super valueForUndefinedKey: key]; } */ - (id) handleQueryWithUnboundKey: (NSString *)key { if ([key isEqualToString: @"window"] || [key isEqualToString: @"_window"]) return [NSNull null]; return [super handleQueryWithUnboundKey: key]; } - (NSMutableArray *) observers { return _observers; } - (void) setObservers: (NSMutableArray *)observers { ASSIGN(_observers, observers); } - (NSWindow *) window { return _window; } - (void) setWindow: (NSWindow *)window { _window = window; } - (void) validate { if (_validating == NO) { _validating = YES; NS_DURING { [_observers makeObjectsPerformSelector: @selector(_validate:) withObject: _window]; _validating = NO; } NS_HANDLER { _validating = NO; NSLog(@"Problem validating toolbar: %@", localException); } NS_ENDHANDLER } } - (void) mouseEntered: (NSEvent *)event { _inside = YES; [self scheduledValidate]; } - (void) mouseExited: (NSEvent *)event { _inside = NO; if ([_validationTimer isValid]) { [_validationTimer invalidate]; _validationTimer = nil; } } - (void) windowDidUpdate: (NSNotification *)notification { // Validate the toolbar for each update of the window. [self validate]; } - (void) scheduledValidate { if (!_inside) return; [self validate]; _validationTimer = [NSTimer timerWithTimeInterval: ValidationInterval target: self selector: @selector(scheduledValidate) userInfo: nil repeats: NO]; [[NSRunLoop currentRunLoop] addTimer: _validationTimer forMode: NSDefaultRunLoopMode]; } @end @implementation GSValidationCenter + (GSValidationCenter *) sharedValidationCenter { if (vc == nil) { if ((vc = [[GSValidationCenter alloc] init]) != nil) { // Nothing special } } return vc; } - (id) init { if ((self = [super init]) != nil) { _vobjs = [[NSMutableArray alloc] init]; } return self; } - (void) dealloc { [nc removeObserver: self]; RELEASE(_vobjs); [super dealloc]; } - (GSValidationObject *) validationObjectForWindow: (NSWindow*)w { return [[_vobjs objectsWithValue: w forKey: @"_window"] objectAtIndex: 0]; } - (NSArray *) observersWindow: (NSWindow *)window { int i; NSArray *observersArray; NSMutableArray *result; if (window == nil) { result = [NSMutableArray array]; observersArray = [_vobjs valueForKey: @"_observers"]; for (i = 0; i < [observersArray count]; i++) { [result addObjectsFromArray: [observersArray objectAtIndex: i]]; } return result; } else { return [[self validationObjectForWindow: window] observers]; } } - (void) addObserver: (id)observer window: (NSWindow *)window { GSValidationObject *vobj = [self validationObjectForWindow: window]; NSMutableArray *observersWindow = nil; if (window == nil) return; if (vobj != nil) { observersWindow = [vobj observers]; } else { vobj = [[GSValidationObject alloc] initWithWindow: window]; [_vobjs addObject: vobj]; RELEASE(vobj); observersWindow = [NSMutableArray array]; [vobj setObservers: observersWindow]; } [observersWindow addObject: observer]; } - (void) removeObserver: (id)observer window: (NSWindow *)window { GSValidationObject *vobj; NSMutableArray *observersWindow; NSArray *windows; NSEnumerator *e; NSWindow *w; if (window == nil) { windows = [_vobjs valueForKey: @"_window"]; } else { windows = [NSArray arrayWithObject: window]; } e = [windows objectEnumerator]; while ((w = [e nextObject]) != nil) { vobj = [self validationObjectForWindow: w]; observersWindow = [vobj observers]; if (observersWindow != nil && [observersWindow containsObject: observer]) { [observersWindow removeObject: observer]; if ([observersWindow count] == 0) { [vobj clean]; [_vobjs removeObjectIdenticalTo: vobj]; } } } } - (void) windowWillClose: (NSNotification *)notification { GSValidationObject *vobj; // NSLog(@"Window will close"); vobj = [self validationObjectForWindow: [notification object]]; if (vobj != nil) { [vobj clean]; [_vobjs removeObjectIdenticalTo: vobj]; } } @end // --- @implementation NSToolbar // Class methods // Initialize the class when it is loaded + (void) initialize { if (self == [NSToolbar class]) { [self setVersion: current_version]; nc = [NSNotificationCenter defaultCenter]; vc = [GSValidationCenter sharedValidationCenter]; toolbars = [[NSMutableArray alloc] init]; } } // Private class method to access static variable toolbars in subclasses + (NSArray *) _toolbarsWithIdentifier: (NSString *)identifier { return [toolbars objectsWithValue: identifier forKey: @"_identifier"]; } // Instance methods - (id) initWithIdentifier: (NSString *)identifier { NSToolbar *toolbarModel = nil; if ((self = [super init]) == nil) return nil; ASSIGN(_identifier, identifier); _items = [[NSMutableArray alloc] init]; // Only set when loaded from a nib _interfaceBuilderItemsByIdentifier = nil; _interfaceBuilderAllowedItemIdentifiers = nil; _interfaceBuilderDefaultItemIdentifiers = nil; _interfaceBuilderSelectableItemIdentifiers = nil; toolbarModel = [self _toolbarModel]; if (toolbarModel != nil) { _customizationPaletteIsRunning = NO; _allowsUserCustomization = [toolbarModel allowsUserCustomization]; _autosavesConfiguration = [toolbarModel autosavesConfiguration]; ASSIGN(_configurationDictionary, [toolbarModel configurationDictionary]); _displayMode = [toolbarModel displayMode]; _sizeMode = [toolbarModel sizeMode]; _visible = [toolbarModel isVisible]; } else { _customizationPaletteIsRunning = NO; _allowsUserCustomization = NO; _autosavesConfiguration = NO; _configurationDictionary = nil; _displayMode = NSToolbarDisplayModeIconAndLabel; _sizeMode = NSToolbarSizeModeRegular; _visible = YES; } _delegate = nil; [self setShowsBaselineSeparator: YES]; // Store in list of toolbars [toolbars addObject: self]; return self; } - (NSArray *) _identifiersForItems: (NSArray*)items { NSMutableArray *result = [NSMutableArray arrayWithCapacity: [items count]]; NSEnumerator *e = [items objectEnumerator]; NSToolbarItem *item; if (items == nil) return nil; while ((item = [e nextObject]) != nil) { [result addObject: [item itemIdentifier]]; } return result; } - (void) encodeWithCoder: (NSCoder*)aCoder { if ([aCoder allowsKeyedCoding]) { // FIXME } else { // FIXME } } - (id) initWithCoder: (NSCoder *)aCoder { if ([aCoder allowsKeyedCoding]) { ASSIGN(_identifier, [aCoder decodeObjectForKey:@"NSToolbarIdentifier"]); _items = [[NSMutableArray alloc] init]; ASSIGN(_interfaceBuilderItemsByIdentifier, [aCoder decodeObjectForKey: @"NSToolbarIBIdentifiedItems"]); ASSIGN(_interfaceBuilderAllowedItemIdentifiers, [self _identifiersForItems: [aCoder decodeObjectForKey: @"NSToolbarIBAllowedItems"]]); ASSIGN(_interfaceBuilderDefaultItemIdentifiers, [self _identifiersForItems: [aCoder decodeObjectForKey: @"NSToolbarIBDefaultItems"]]); ASSIGN(_interfaceBuilderSelectableItemIdentifiers, [self _identifiersForItems: [aCoder decodeObjectForKey: @"NSToolbarIBSelectableItems"]]); _customizationPaletteIsRunning = NO; _configurationDictionary = nil; [self setAllowsUserCustomization: [aCoder decodeBoolForKey: @"NSToolbarAllowsUserCustomization"]]; [self setAutosavesConfiguration: [aCoder decodeBoolForKey: @"NSToolbarAutosavesConfiguration"]]; [self setDisplayMode: [aCoder decodeIntForKey: @"NSToolbarDisplayMode"]]; [self setShowsBaselineSeparator: [aCoder decodeBoolForKey: @"NSToolbarShowsBaselineSeparator"]]; [self setSizeMode: [aCoder decodeIntForKey: @"NSToolbarSizeMode"]]; [self setVisible: [aCoder decodeBoolForKey: @"NSToolbarPrefersToBeShown"]]; [self setDelegate: [aCoder decodeObjectForKey: @"NSToolbarDelegate"]]; } else { // FIXME } // Store in list of toolbars [toolbars addObject: self]; return self; } - (void) dealloc { //NSLog(@"Toolbar dealloc %@", self); [self _setToolbarView: nil]; // Use DESTROY ? RELEASE(_identifier); RELEASE(_selectedItemIdentifier); RELEASE(_configurationDictionary); RELEASE(_items); DESTROY(_interfaceBuilderItemsByIdentifier); DESTROY(_interfaceBuilderAllowedItemIdentifiers); DESTROY(_interfaceBuilderDefaultItemIdentifiers); DESTROY(_interfaceBuilderSelectableItemIdentifiers); if (_delegate != nil) { [nc removeObserver: _delegate name: nil object: self]; _delegate = nil; } [super dealloc]; } // FIXME: Hack - (oneway void) release { // When a toolbar has no external references any more, it's necessary // to remove the toolbar from the master list, so that it // doesn't cause a memory leak. if ([self retainCount] == 2) [toolbars removeObjectIdenticalTo: self]; [super release]; } - (id) valueForUndefinedKey: (NSString *)key { if ([key isEqualToString: @"window"] || [key isEqualToString: @"_window"]) return nil; return [super valueForUndefinedKey: key]; } - (void) insertItemWithItemIdentifier: (NSString *)itemIdentifier atIndex: (NSInteger)index { [self _insertItemWithItemIdentifier: itemIdentifier atIndex: index broadcast: YES]; } - (void) removeItemAtIndex: (NSInteger)index { [self _removeItemAtIndex: index broadcast: YES]; } - (void) runCustomizationPalette: (id)sender { GSToolbarCustomizationPalette *palette; if (![self allowsUserCustomization]) { return; } if (_customizationPaletteIsRunning) { NSLog(@"Customization palette is already running for toolbar: %@", self); return; } if (!_visible) { [self setVisible:YES]; } palette = [GSToolbarCustomizationPalette palette]; if (palette != nil) _customizationPaletteIsRunning = YES; [palette showForToolbar: self]; } - (void) validateVisibleItems { NSEnumerator *e = [[self visibleItems] objectEnumerator]; NSToolbarItem *item = nil; while ((item = [e nextObject]) != nil) { [item validate]; } } // Accessors - (BOOL) allowsUserCustomization { return _allowsUserCustomization; } - (BOOL) autosavesConfiguration { return _autosavesConfiguration; } - (NSDictionary *) configurationDictionary { return _configurationDictionary; } - (BOOL) customizationPaletteIsRunning { return _customizationPaletteIsRunning; } - (void) _setCustomizationPaletteIsRunning: (BOOL)isRunning { _customizationPaletteIsRunning = isRunning; } - (id) delegate { return _delegate; } - (NSToolbarDisplayMode) displayMode { return _displayMode; } - (NSString *) identifier { return _identifier; } - (NSArray *) items { return _items; } - (NSString *) selectedItemIdentifier { return _selectedItemIdentifier; } - (BOOL) showsBaselineSeparator { return _showsBaselineSeparator; } - (NSArray *) visibleItems { if ([_toolbarView superview] == nil) { return nil; } else { return [[_toolbarView _visibleBackViews] valueForKey: @"toolbarItem"]; } } - (void) setAllowsUserCustomization: (BOOL)flag { [self _setAllowsUserCustomization: flag broadcast: YES]; } - (void) setAutosavesConfiguration: (BOOL)flag { [self _setAutosavesConfiguration: flag broadcast: YES]; } - (void) setConfigurationFromDictionary: (NSDictionary *)configDict { int i = 0; id item = nil; NSArray *items = nil; NSEnumerator *en = nil; ASSIGN(_configurationDictionary, configDict); // set _visible = [[_configurationDictionary objectForKey: @"isVisible"] boolValue]; _displayMode = [[_configurationDictionary objectForKey: @"displayMode"] intValue]; // remove all items... for(i = 0; i < [_items count]; i++) { [self _removeItemAtIndex: 0 broadcast: YES]; } // add all of the items... i = 0; items = [_configurationDictionary objectForKey: @"items"]; en = [items objectEnumerator]; while((item = [en nextObject]) != nil) { [self _insertItemWithItemIdentifier: item atIndex: i++ broadcast: YES]; } } /** * Sets the receivers delegate ... this is the object which will receive * -toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar: * -toolbarAllowedItemIdentifiers: and -toolbarDefaultItemIdentifiers: * messages. */ - (void) setDelegate: (id)delegate { if (_delegate == delegate) return; if (_delegate != nil) [nc removeObserver: _delegate name: nil object: self]; // Assign the delegate... _delegate = delegate; if (_delegate != nil) { #define CHECK_REQUIRED_METHOD(selector_name) \ if (![_delegate respondsToSelector: @selector(selector_name)]) \ [NSException raise: NSInternalInconsistencyException \ format: @"delegate does not respond to %@",@#selector_name] if (_interfaceBuilderItemsByIdentifier == nil) { CHECK_REQUIRED_METHOD(toolbar:itemForItemIdentifier: willBeInsertedIntoToolbar:); } if (_interfaceBuilderAllowedItemIdentifiers == nil) { CHECK_REQUIRED_METHOD(toolbarAllowedItemIdentifiers:); } if (_interfaceBuilderDefaultItemIdentifiers == nil) { CHECK_REQUIRED_METHOD(toolbarDefaultItemIdentifiers:); } #define SET_DELEGATE_NOTIFICATION(notif_name) \ if ([_delegate respondsToSelector: @selector(toolbar##notif_name:)]) \ [nc addObserver: _delegate \ selector: @selector(toolbar##notif_name:) \ name: NSToolbar##notif_name##Notification object: self] SET_DELEGATE_NOTIFICATION(DidRemoveItem); SET_DELEGATE_NOTIFICATION(WillAddItem); } [self _build]; } - (NSArray *) _selectableItemIdentifiers { NSArray *selectableIdentifiers = nil; if (_delegate != nil && [_delegate respondsToSelector: @selector(toolbarSelectableItemIdentifiers:)]) { selectableIdentifiers = [_delegate toolbarSelectableItemIdentifiers: self]; if (selectableIdentifiers == nil) { NSLog(@"Toolbar delegate returns no such selectable item identifiers"); } } if (selectableIdentifiers == nil) { selectableIdentifiers = _interfaceBuilderSelectableItemIdentifiers; } return selectableIdentifiers; } - (void) setSelectedItemIdentifier: (NSString *)identifier { NSArray *selectedItems; NSArray *itemsToSelect; NSEnumerator *e; NSToolbarItem *item; NSArray *selectableIdentifiers; BOOL updated = NO; // First, we have to deselect the previous selected toolbar items selectedItems = [[self items] objectsWithValue: [self selectedItemIdentifier] forKey: @"_itemIdentifier"]; e = [selectedItems objectEnumerator]; while ((item = [e nextObject]) != nil) { [item _setSelected: NO]; } selectableIdentifiers = [self _selectableItemIdentifiers]; if (selectableIdentifiers == nil) return; itemsToSelect = [_items objectsWithValue: identifier forKey: @"_itemIdentifier"]; e = [itemsToSelect objectEnumerator]; while ((item = [e nextObject]) != nil) { if ([selectableIdentifiers containsObject: [item itemIdentifier]]) { if (![item _selected]) [item _setSelected: YES]; updated = YES; } } if (updated) { ASSIGN(_selectedItemIdentifier, identifier); } else { NSLog(@"Toolbar delegate returns no such selectable item identifiers"); } } - (NSToolbarSizeMode) sizeMode { return _sizeMode; } - (BOOL) isVisible { return _visible; } /** * Sets the receivers delegate ... this is the object which will receive * -toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar: * -toolbarAllowedItemIdentifiers: and -toolbarDefaultItemIdentifiers: * messages. */ - (void) setDisplayMode: (NSToolbarDisplayMode)displayMode { [self _setDisplayMode: displayMode broadcast: YES]; } - (void) setSizeMode: (NSToolbarSizeMode)sizeMode { [self _setSizeMode: sizeMode broadcast: YES]; } - (void) setVisible: (BOOL)shown { [self _setVisible: shown broadcast: NO]; } - (void) setShowsBaselineSeparator: (BOOL)flag { _showsBaselineSeparator = flag; if (_showsBaselineSeparator) [_toolbarView setBorderMask: GSToolbarViewBottomBorder]; else [_toolbarView setBorderMask: 0]; } // Private methods - (NSArray *) _defaultItemIdentifiers { if (_delegate != nil) { return [_delegate toolbarDefaultItemIdentifiers:self]; } else { return _interfaceBuilderDefaultItemIdentifiers; } } /* * Toolbar build : * will use the delegate when there is no toolbar model */ - (void) _build { NSToolbar *toolbarModel; NSArray *wantedItemIdentifiers; NSEnumerator *e; id itemIdentifier; int i = 0; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSString *tableKey = [NSString stringWithFormat: @"NSToolbar Config %@",_identifier]; NSDictionary *config = [defaults objectForKey: tableKey]; if (config) { NSToolbarDisplayMode displayMode = 0; NSToolbarSizeMode sizeMode = 0; displayMode = (NSToolbarDisplayMode)[[config objectForKey: @"displayMode"] intValue]; [self setDisplayMode: displayMode]; sizeMode = (NSToolbarSizeMode)[[config objectForKey: @"sizeMode"] intValue]; [self setSizeMode: sizeMode]; } // Switch off toolbar view reload _build = YES; RELEASE(_items); _items = [[NSMutableArray alloc] init]; toolbarModel = [self _toolbarModel]; wantedItemIdentifiers = [self _itemsFromConfig]; if(wantedItemIdentifiers == nil) { if (toolbarModel != nil) { wantedItemIdentifiers = [[toolbarModel items] valueForKey: @"_itemIdentifier"]; } else { wantedItemIdentifiers = [self _defaultItemIdentifiers]; } } if (wantedItemIdentifiers == nil) { _build = NO; return; } e = [wantedItemIdentifiers objectEnumerator]; while ((itemIdentifier = [e nextObject]) != nil) { [self _insertItemWithItemIdentifier: itemIdentifier atIndex: [_items count] broadcast: NO]; i++; } _build = NO; // Now do the toolbar view reload if (_toolbarView != nil) [_toolbarView _reload]; } - (void) _resetConfig { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSString *tableKey = [NSString stringWithFormat: @"NSToolbar Config %@",_identifier]; [defaults removeObjectForKey: tableKey]; [self _build]; } - (BOOL) usesStandardBackgroundColor { return [_toolbarView _usesStandardBackgroundColor]; } - (void) setUsesStandardBackgroundColor: (BOOL)standard { [_toolbarView _setUsesStandardBackgroundColor: standard]; } - (int) _indexOfItem: (NSToolbarItem *)item { return [_items indexOfObjectIdenticalTo: item]; } - (void) _insertPassivelyItem: (NSToolbarItem *)item atIndex: (int)newIndex { if (![_items containsObject: item]) { if (newIndex > [_items count] - 1) { newIndex = [_items count] - 1; } else if(newIndex < 0) { newIndex = 0; } [_items insertObject: item atIndex: newIndex]; } else { NSLog(@"Error: the toolbar already contains the item to insert."); } } - (void) _saveConfig { if (_identifier != nil) { NSUserDefaults *defaults; NSString *tableKey; id config; NSMutableArray *items = [NSMutableArray array]; id item; NSEnumerator *en = [_items objectEnumerator]; defaults = [NSUserDefaults standardUserDefaults]; tableKey = [NSString stringWithFormat: @"NSToolbar Config %@",_identifier]; config = [defaults objectForKey: tableKey]; if (config == nil) { config = [NSMutableDictionary dictionary]; } else { config = [config mutableCopy]; } [config setObject: [NSNumber numberWithBool: _visible] forKey: @"isVisible"]; [config setObject: [NSNumber numberWithInt: _displayMode] forKey: @"displayMode"]; [config setObject: [NSNumber numberWithInt: _sizeMode] forKey: @"sizeMode"]; while((item = [en nextObject]) != nil) { [items addObject: [item itemIdentifier]]; } [config setObject: items forKey: @"items"]; // write to defaults [defaults setObject: config forKey: tableKey]; ASSIGN(_configurationDictionary, config); } } - (BOOL) _containsItemWithIdentifier: (NSString *)identifier { NSEnumerator *en = [_items objectEnumerator]; id item = nil; while((item = [en nextObject]) != nil) { if([identifier isEqual: [item itemIdentifier]]) { return YES; } } return NO; } - (NSArray *) _itemsFromConfig { NSArray *items = nil; if (_identifier != nil) { NSUserDefaults *defaults; NSString *tableKey; id config; defaults = [NSUserDefaults standardUserDefaults]; tableKey = [NSString stringWithFormat: @"NSToolbar Config %@",_identifier]; config = [defaults objectForKey: tableKey]; if (config != nil) { items = [config objectForKey: @"items"]; } } return items; } - (NSToolbar *) _toolbarModel { NSArray *linked; id toolbar; linked = [[self class] _toolbarsWithIdentifier: [self identifier]]; if (linked != nil && [linked count] > 0) { toolbar = [linked objectAtIndex: 0]; // Toolbar model class must be identical to self class : // an NSToolbar instance cannot use a NSToolbar instance as a model if ([toolbar isMemberOfClass: [self class]] && toolbar != self) return toolbar; else return nil; } return nil; } - (NSToolbarItem *) _toolbarItemForIdentifier: (NSString *)itemIdent willBeInsertedIntoToolbar: (BOOL)insert { NSToolbarItem *item = nil; if ([itemIdent isEqual: NSToolbarSeparatorItemIdentifier] || [itemIdent isEqual: NSToolbarSpaceItemIdentifier] || [itemIdent isEqual: NSToolbarFlexibleSpaceItemIdentifier] || [itemIdent isEqual: NSToolbarShowColorsItemIdentifier] || [itemIdent isEqual: NSToolbarShowFontsItemIdentifier] || [itemIdent isEqual: NSToolbarCustomizeToolbarItemIdentifier] || [itemIdent isEqual: NSToolbarPrintItemIdentifier]) { item = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdent] autorelease]; } if (item == nil && _delegate != nil) { item = [_delegate toolbar: self itemForItemIdentifier: itemIdent willBeInsertedIntoToolbar: insert]; } if (item == nil && _interfaceBuilderItemsByIdentifier) { item = [_interfaceBuilderItemsByIdentifier objectForKey: itemIdent]; } return item; } /* * * The methods below handles the toolbar edition and broadcasts each associated * event to the other toolbars with identical identifiers. * Warning : broadcast process only happens between instances based on the same * class. */ #define TRANSMIT(signature) \ NSEnumerator *e = [[NSToolbar _toolbarsWithIdentifier: _identifier] objectEnumerator]; \ NSToolbar *toolbar; \ \ while ((toolbar = [e nextObject]) != nil) \ { \ if (toolbar != self && [self isMemberOfClass: [self class]]) \ [toolbar signature]; \ } \ - (NSArray *) _allowedItemIdentifiers { if (_delegate) { return [_delegate toolbarAllowedItemIdentifiers: self]; } else { return _interfaceBuilderAllowedItemIdentifiers; } } - (void) _insertItemWithItemIdentifier: (NSString *)itemIdentifier atIndex: (int)index broadcast: (BOOL)broadcast { NSToolbarItem *item = nil; NSArray *allowedItems = [self _allowedItemIdentifiers]; if ([allowedItems containsObject: itemIdentifier]) { item = [self _toolbarItemForIdentifier: itemIdentifier willBeInsertedIntoToolbar: YES]; if (item != nil) { NSArray *selectableItems = [self _selectableItemIdentifiers]; if ([selectableItems containsObject: itemIdentifier]) [item _setSelectable: YES]; [nc postNotificationName: NSToolbarWillAddItemNotification object: self userInfo: [NSDictionary dictionaryWithObject: item forKey: @"item"]]; [item _setToolbar: self]; [item _layout]; [_items insertObject: item atIndex: index]; // We reload the toolbarView each time a new item is added except when // we build/create the toolbar if (!_build) [_toolbarView _reload]; if (broadcast) { TRANSMIT(_insertItemWithItemIdentifier: itemIdentifier atIndex: index broadcast: NO); } } } } - (void) _removeItemAtIndex: (int)index broadcast: (BOOL)broadcast { id item = [_items objectAtIndex: index]; RETAIN(item); [self _performRemoveItem: item]; [self _concludeRemoveItem: item atIndex: index broadcast: broadcast]; RELEASE(item); } - (void) _performRemoveItem: (NSToolbarItem *)item { [_items removeObject: item]; [_toolbarView _reload]; [self _saveConfig]; } - (void) _concludeRemoveItem: (NSToolbarItem *)item atIndex: (int)index broadcast: (BOOL)broadcast { [nc postNotificationName: NSToolbarDidRemoveItemNotification object: self userInfo: [NSDictionary dictionaryWithObject: item forKey: @"item"]]; if (broadcast) { TRANSMIT(_removeItemAtIndex: index broadcast: NO); } } - (void) _setAllowsUserCustomization: (BOOL)flag broadcast: (BOOL)broadcast { _allowsUserCustomization = flag; if (broadcast) { TRANSMIT(_setAllowsUserCustomization: _allowsUserCustomization broadcast: NO); } } - (void) _setAutosavesConfiguration: (BOOL)flag broadcast: (BOOL)broadcast { _autosavesConfiguration = flag; if (broadcast) { TRANSMIT(_setAutosavesConfiguration: _autosavesConfiguration broadcast: NO); } } - (void) _setConfigurationFromDictionary: (NSDictionary *)configDict broadcast: (BOOL)broadcast { ASSIGN(_configurationDictionary, configDict); if (broadcast) { TRANSMIT(_setConfigurationFromDictionary: _configurationDictionary broadcast: NO); } } - (void) _moveItemFromIndex: (int)index toIndex: (int)newIndex broadcast: (BOOL)broadcast { id item; item = RETAIN([_items objectAtIndex: index]); [_items removeObjectAtIndex: index]; if (newIndex > [_items count] - 1) { [_items addObject: item]; } else { [_items insertObject: item atIndex: newIndex]; } [_toolbarView _reload]; RELEASE(item); if (broadcast) { TRANSMIT(_moveItemFromIndex: index toIndex: newIndex broadcast: NO); } } - (void) _setDisplayMode: (NSToolbarDisplayMode)displayMode broadcast: (BOOL)broadcast { if (_displayMode != displayMode) { _displayMode = displayMode; if ([self isVisible]) { [_toolbarView _reload]; [(id)[[_toolbarView window] _windowView] adjustToolbarView: _toolbarView]; } if (broadcast) { TRANSMIT(_setDisplayMode: _displayMode broadcast: NO); } } } - (void) _setSizeMode: (NSToolbarSizeMode)sizeMode broadcast: (BOOL)broadcast { if (_sizeMode != sizeMode) { _sizeMode = sizeMode; if ([self isVisible]) { [_toolbarView _reload]; [(id)[[_toolbarView window] _windowView] adjustToolbarView: _toolbarView]; } if (broadcast) { TRANSMIT(_setSizeMode: _sizeMode broadcast: NO); } } } - (NSWindow*) _window { NSWindow *window = [_toolbarView window]; NSEnumerator *wenum; if (window) return window; wenum = [GSAllWindows() objectEnumerator]; while ((window = [wenum nextObject])) { if ([window toolbar] == self) return window; } return nil; } - (void) _setVisible: (BOOL)shown broadcast: (BOOL)broadcast { if (_visible != shown) { _visible = shown; if (shown) [self _build]; if (shown) { if ((_toolbarView == nil) || ([_toolbarView superview] == nil)) { NSWindow *w = [self _window]; [(id)[w _windowView] addToolbarView: [self _toolbarView]]; } } else { if ((_toolbarView != nil) && ([_toolbarView superview] != nil)) { NSWindow *w = [self _window]; [(id)[w _windowView] removeToolbarView: [self _toolbarView]]; } } if (broadcast) { TRANSMIT(_setVisible: _visible broadcast: NO); } } } // Private Accessors - (void) _setToolbarView: (GSToolbarView *)toolbarView { if (_toolbarView == toolbarView) return; if (_toolbarView != nil) { // We unset the toolbar from the previous toolbar view [_toolbarView setToolbar: nil]; [vc removeObserver: self window: nil]; } ASSIGN(_toolbarView, toolbarView); if (toolbarView != nil) { // In the case the window parameter is a nil value, nothing happens. [vc addObserver: self window: [toolbarView window]]; // We set the toolbar on the new toolbar view [_toolbarView setToolbar: self]; } } - (GSToolbarView *) _toolbarView { if (_toolbarView == nil) { // Instantiate the toolbar view // addToolbarView: method will set the toolbar view to the right // frame GSToolbarView *toolbarView = [[GSToolbarView alloc] initWithFrame: NSMakeRect(0, 0, 100, 100)]; [toolbarView setAutoresizingMask: NSViewWidthSizable | NSViewMinYMargin]; if (_showsBaselineSeparator) [toolbarView setBorderMask: GSToolbarViewBottomBorder]; else [toolbarView setBorderMask: 0]; // Load the toolbar view inside the toolbar _toolbarView = toolbarView; [_toolbarView setToolbar: self]; } return _toolbarView; } - (void) _toolbarViewWillMoveToSuperview: (NSView *)newSuperview { // Must synchronize the validation system // _toolbarView should never be nil here // We don't handle synchronization when the toolbar view is added to a superview not // binded to a window, such superview being later moved to a window. (FIX ME ?) // NSLog(@"Moving to window %@", [newSuperview window]); [vc removeObserver: self window: nil]; if (newSuperview != nil) [vc addObserver: self window: [newSuperview window]]; } - (void) _validate: (NSWindow *)observedWindow { // We observe only one window, then we ignore observedWindow. [self validateVisibleItems]; } @end