/* 
   <title>GSToolbar.m</title>

   <abstract>The basic toolbar class.</abstract>
   
   Copyright (C) 2004 Free Software Foundation, Inc.

   Author:  Gregory John Casamento <greg_casamento@yahoo.com>,
            Fabien Vallon <fabien.vallon@fr.alcove.com>,
	    Quentin Mathe <qmathe@club-internet.fr>
   Date: February 2004
   
   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 Library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this library; see the file COPYING.LIB.
   If not, write to the Free Software Foundation,
   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/ 

#include <Foundation/NSObject.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSException.h>
#include <Foundation/NSNotification.h>
#include <Foundation/NSUserDefaults.h>
#include "AppKit/NSApplication.h"
#include "AppKit/NSToolbarItem.h"
#include "AppKit/NSView.h"
#include "AppKit/NSClipView.h"
#include "AppKit/NSButton.h"
#include "AppKit/NSNibLoading.h"
#include "AppKit/NSBezierPath.h"
#include "AppKit/NSImage.h"
#include "AppKit/NSMenu.h"
#include "AppKit/NSEvent.h"
#include "AppKit/NSWindow.h"
#include "AppKit/NSWindow+Toolbar.h"
#include "GNUstepGUI/GSToolbarView.h"
#include "GNUstepGUI/GSToolbar.h"

// internal
static NSNotificationCenter *nc = nil;
static const int current_version = 1;

static NSMutableArray *toolbars;

// Validation stuff
static const unsigned int ValidationInterval = 10;
static id validationCenter;

// Extensions
@implementation NSArray (ObjectsWithValueForKey)

- (NSArray *) objectsWithValue: (id)value forKey: (NSString *)key 
{
  NSMutableArray *result = [[NSMutableArray alloc] init];
  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;
  
  AUTORELEASE(result);
  
  return result;
}
@end

// Validation support

@interface GSValidationObject : NSObject
{
  NSMutableArray *_observers;
  NSView *_view;
}

- (id) initWithView: (NSView *)view;
- (NSArray *) observers;
- (void) addObserver: (id)observer;
- (void) removeObserver: (id)observer;
- (void) validate;
- (NSView *) view;
@end

@interface GSValidationManager : NSObject
{
  NSWindow *_window;
  NSView *_trackingRectView;
  NSTrackingRectTag _trackingRect;
  NSTimer *_validationTimer;
  BOOL _inside;
}

- (id) initWithWindow: (NSWindow *)window;
- (void) validate;

- (void) invalidate;

// Tracking rect methods
- (void) mouseEntered: (NSEvent *)event;
- (void) mouseExited: (NSEvent *)event;
@end

@interface GSValidationCenter : NSObject
{
  NSMutableArray *_validationManagers;
  NSMutableArray *_validationObjects;
  NSWindow *_prevWindow;
}

+ (GSValidationCenter *) sharedValidationCenter;
- (id) init;
- (void) viewWillMove: (NSNotification *)notification;
- (void) viewDidMove: (NSNotification *)notification;
- (void) unregisterValidationObject: (GSValidationObject *)validationObject;
- (GSValidationObject *) validationObjectWithView: (NSView *)view;

// Private methods
- (GSValidationManager *) _validationManagerWithWindow: (NSWindow *)window;
- (NSArray *) _validationObjectsWithWindow: (NSWindow *)window;
@end

// Validation mechanism

@implementation GSValidationObject

- (id) initWithView: (NSView *)view
{
  if ((self = [super init]) != nil)
    {
      ASSIGN(_view, view);
      _observers = [[NSMutableArray alloc] init];
    }
  return self;
}

- (void) dealloc
{
  RELEASE(_view);
  RELEASE(_observers);
  [super dealloc];
}

- (NSArray *) observers
{
  return _observers;
}

- (void) addObserver: (id)observer
{
  [_observers addObject: observer];
}

- (void) removeObserver: (id)observer
{
  if ([_observers containsObject: observer])
    [_observers removeObject: observer];
}

- (void) validate
{ 
  if ([_view superview] != nil)
    {
      [_observers makeObjectsPerformSelector: @selector(_validate)];
    }
}

- (NSView *) view
{
  return _view;
}

@end

@interface NSWindow (GNUstepPrivate)
- (NSView *) _windowView;
@end

@implementation GSValidationManager

- (id) initWithWindow: (NSWindow *)window
{
  if ((self = [super init]) != nil)
    {
      NSView *vw;

      vw = [window _windowView];
      
      ASSIGN(_window, window);
      [nc addObserver: self selector: @selector(windowDidUpdate:) 
        name: NSWindowDidUpdateNotification object: _window];
      [nc addObserver: self selector: @selector(windowWillClose:) 
        name: NSWindowWillCloseNotification object: _window];
						
      ASSIGN(_trackingRectView, vw);
      _trackingRect 
	= [_trackingRectView addTrackingRect: [_trackingRectView bounds]
			     owner: self 
			     userData: nil 
			     assumeInside: NO];
    }
    
  return self;
}

- (void) invalidate
{
  [_trackingRectView removeTrackingRect: _trackingRect]; 
  // tracking rect retains us, that is normal ?
  
  [_validationTimer invalidate]; // timer idem
}

- (void) dealloc
{
  RELEASE(_trackingRectView);
  RELEASE(_window);
  
  [super dealloc];
}

- (void) windowWillClose: (NSNotification *)notification
{
  GSValidationCenter *vc = [GSValidationCenter sharedValidationCenter];
  NSEnumerator *e;
  id vo;
  
  [nc removeObserver: self];
 
  e = [[vc _validationObjectsWithWindow: _window] objectEnumerator];
  while ((vo = [e nextObject]) != nil)
    {
      [vc unregisterValidationObject: vo];
    }
}

- (void) mouseEntered: (NSEvent *)event
{ 
  _inside = YES;
  if (_validationTimer == nil || ![_validationTimer isValid])
    {
      _validationTimer = [NSTimer timerWithTimeInterval: ValidationInterval 
                                                 target: self 
					       selector: @selector(validate) 
					       userInfo: nil 
					        repeats: YES];
      [[NSRunLoop currentRunLoop] addTimer: _validationTimer 
                                   forMode: NSDefaultRunLoopMode];
      [self validate];
    }
}

- (void) mouseExited: (NSEvent *)event
{ 
  _inside = NO;
  [_validationTimer invalidate];
  _validationTimer = nil;
}

- (void) validate
{ 
  [[[GSValidationCenter sharedValidationCenter] 
    _validationObjectsWithWindow: _window]  
    makeObjectsPerformSelector: @selector(validate)];
}


- (void) windowDidUpdate: (NSNotification *)notification
{
  if ([[NSApp currentEvent] type] == NSLeftMouseUp)
    {
      GSValidationManager *validationManager = 
      [[GSValidationCenter sharedValidationCenter] 
        _validationManagerWithWindow: [notification object]];

      [validationManager performSelector: @selector(validate) withObject: nil];
    }
}

@end

@implementation GSValidationCenter

+ (GSValidationCenter *) sharedValidationCenter
{
  if (validationCenter == nil)
    validationCenter = [[GSValidationCenter alloc] init];
    
  return validationCenter;
}

- (id) init
{
  if ((self = [super init]) != nil)
    {
      _validationManagers = [[NSMutableArray alloc] init];
      _validationObjects = [[NSMutableArray alloc] init];
      
      [nc addObserver: self 
             selector: @selector(viewWillMove:) 
	         name: @"GSViewWillMoveToWindow" 
	       object: nil];
      [nc addObserver: self 
             selector: @selector(viewDidMove:) 
	         name: @"GSViewDidMoveToWindow" 
	       object: nil];
    }
  
  return self;
}

- (void) viewWillMove: (NSNotification *)notification
{
  _prevWindow = [[notification object] window];
  RETAIN(_prevWindow);
}

- (void) viewDidMove: (NSNotification *)notification
{
  NSWindow *window = [[notification object] window];
  GSValidationManager *validationManager = 
    [self _validationManagerWithWindow : _prevWindow];
  
  if (validationManager != nil 
    && [[self _validationObjectsWithWindow: _prevWindow] count] == 0)
    {
      [validationManager invalidate];
      [_validationManagers removeObject: validationManager];
    }
    
  if (window == nil)
    return;
  
  validationManager = [self _validationManagerWithWindow: window];
  if (validationManager == nil)
    {
      validationManager = [[GSValidationManager alloc] initWithWindow: window];
      [_validationManagers addObject: validationManager];
      RELEASE(validationManager);
    }
    
  RELEASE(_prevWindow);
}

/* validationObjectWithView: opposite method
 * Remove the object in the validation objects list.
 * Release the validation manager associated to the window (related to the
 * validation object and its view) in the case there are no other validation
 * objects related to this window.
 */
- (void) unregisterValidationObject: (GSValidationObject *)validationObject
{
  int index;
  
  if ((index = [_validationObjects indexOfObject: validationObject]) 
    != NSNotFound) 
    {
      NSWindow *window = [[validationObject view] window];
      
      [_validationObjects removeObjectAtIndex: index];
      
      if ([[self _validationObjectsWithWindow: window] count] == 0)
	{ 
	  GSValidationManager *validationManager = 
	    [self _validationManagerWithWindow: window];
	  [validationManager invalidate];
	  [_validationManagers removeObject: validationManager];
	}
    }
}

/* Return the validation object associated with the parameter view.
 * If there is no such validation object, create it by using view and then check
 * that an associated validation manager (bound to the window which the view
 * depends on) exists.
 * If there is no such validation manager, create it.
 */
- (GSValidationObject *) validationObjectWithView: (NSView *)view
{
  GSValidationObject *validationObject;
  GSValidationManager *validationManager;
  NSWindow *window = [view window];
  
  if (view == nil)
    {
      NSDebugLog(@"Validation object cannot be created because the view is \
        nil"); 
      return nil;
    }
  
  validationObject = [[_validationObjects objectsWithValue: view 
    forKey: @"_view"] objectAtIndex: 0];
  if (validationObject == nil)
    {
      validationObject = [[GSValidationObject alloc] initWithView: view];
      [_validationObjects addObject: validationObject];
      
      if (window == nil)
        return nil;
      
      validationManager = [self _validationManagerWithWindow: window];
      if (validationManager == nil)
        {
	  validationManager = 
	    [[GSValidationManager alloc] initWithWindow: window]; 
	  [_validationManagers addObject: validationManager];
	  RELEASE(validationManager);
	}
    }
  
  return validationObject;
}

// Private methods

- (GSValidationManager *) _validationManagerWithWindow: (NSWindow *)window
{
  GSValidationManager *validationManager = 
    [[_validationManagers objectsWithValue: window forKey: @"_window"] 
      objectAtIndex: 0];									 

  return validationManager;
}

- (NSArray *) _validationObjectsWithWindow: (NSWindow *)window
{
  NSEnumerator *e = [_validationObjects objectEnumerator];
  id validationObject;
  NSMutableArray *array = [NSMutableArray array];
  
  while ((validationObject = [e nextObject]) != nil)
    {
      if ([[validationObject view] window] == window)
        [array addObject: validationObject];
    }
  
  return array;
}

@end

// ---

@interface GSToolbar (GNUstepPrivate)

// Private class method

+ (NSMutableArray *) _toolbars;

- (void) _insertItemWithItemIdentifier: (NSString *)itemIdentifier 
                               atIndex: (int)index 
                             broadcast: (BOOL)broadcast;

// Private methods with broadcast support
- (void) _insertItemWithItemIdentifier: (NSString *)itemIdentifier 
                               atIndex: (int)index 
                             broadcast: (BOOL)broadcast;
- (void) _removeItemAtIndex: (int)index broadcast: (BOOL)broadcast;
- (void) _setAllowsUserCustomization: (BOOL)flag broadcast: (BOOL)broadcast;
- (void) _setAutosavesConfiguration: (BOOL)flag broadcast: (BOOL)broadcast;
- (void) _setConfigurationFromDictionary: (NSDictionary *)configDict 
                               broadcast: (BOOL)broadcast;
- (void) _setDelegate: (id)delegate broadcast: (BOOL)broadcast;

// Few other private methods
- (void) _build;
- (void) _loadConfig;
- (NSToolbarItem *) _toolbarItemForIdentifier: (NSString *)itemIdent;
- (GSToolbar *) _toolbarModel;
- (void) _validate;

// Accessors
- (void) _setToolbarView: (GSToolbarView *)toolbarView;
- (GSToolbarView *) _toolbarView;
- (void) _setWindow: (NSWindow *)window;
- (NSWindow *) _window;
@end

@interface NSToolbarItem (GNUstepPrivate)
- (BOOL) _selectable;
- (void) _setSelectable: (BOOL)selectable;
- (BOOL) _selected;
- (void) _setSelected: (BOOL)selected;
- (void) _setToolbar: (GSToolbar *)toolbar;
@end

@interface GSToolbarView (GNUstepPrivate)
- (void) _reload;

// Accessors
- (NSArray *) _visibleBackViews;
- (BOOL) _usesStandardBackgroundColor;
- (BOOL) _willBeVisible;
- (void) _setUsesStandardBackgroundColor: (BOOL)standard;
- (void) _setWillBeVisible: (BOOL)willBeVisible;
@end

// ---

@implementation GSToolbar

// Class methods

// Initialize the class when it is loaded
+ (void) initialize
{
  if (self == [GSToolbar class])
    {
      [self setVersion: current_version];
      nc = [NSNotificationCenter defaultCenter];
      toolbars = [[NSMutableArray alloc] init];
    }
}

// Private class method to access static variable toolbars in subclasses

+ (NSMutableArray *) _toolbars
{
  return toolbars;
}

// Instance methods

- (id) initWithIdentifier: (NSString *)identifier
{
  return [self initWithIdentifier: identifier 
                      displayMode: NSToolbarDisplayModeIconAndLabel 
		         sizeMode: NSToolbarSizeModeRegular];
}


// default initialiser
- (id) initWithIdentifier: (NSString *)identifier 
              displayMode: (NSToolbarDisplayMode)displayMode 
                 sizeMode: (NSToolbarSizeMode)sizeMode
{
  GSToolbar *toolbarModel;
  
  if ((self = [super init]) == nil) 
    return nil;
  
  ASSIGN(_identifier, identifier);
  
  _items = [[NSMutableArray alloc] init];
    
  toolbarModel = [self _toolbarModel];
  
  if (toolbarModel != nil)
    {
      _customizationPaletteIsRunning = NO;
      _allowsUserCustomization = [toolbarModel allowsUserCustomization];
      _autosavesConfiguration = [toolbarModel autosavesConfiguration];
      ASSIGN(_configurationDictionary, [toolbarModel configurationDictionary]);
      
      if ([toolbarModel displayMode] != displayMode 
        && [toolbarModel sizeMode] != sizeMode)
	{
	  // raise an exception.
	}
	
      //[self _loadConfig];
    }
  else
    {
      _customizationPaletteIsRunning = NO;
      _allowsUserCustomization = NO;
      _autosavesConfiguration = NO;
      _configurationDictionary = nil;

      //[self _loadConfig];
    
       _delegate = nil;
    }
    
  _displayMode = displayMode; 
  _sizeMode = sizeMode;
  
  [toolbars addObject: self];
  
  return self;
}

- (void) dealloc
{
  // use DESTROY ?
  RELEASE(_identifier);
  RELEASE(_selectedItemIdentifier);
  RELEASE(_configurationDictionary);
  RELEASE(_items);

  if (_delegate != nil)
    {
      [nc removeObserver: _delegate  name: nil  object: self];
      _delegate = nil;
    }

  [super dealloc];
}

- (void) insertItemWithItemIdentifier: (NSString *)itemIdentifier
			      atIndex: (int)index
{
  [self _insertItemWithItemIdentifier: itemIdentifier 
                              atIndex: index 
                            broadcast: YES]; 
}

- (void) removeItemAtIndex: (int)index
{
  [self _removeItemAtIndex: index broadcast: YES];
}

- (void) runCustomizationPalette: (id)sender
{
  _customizationPaletteIsRunning = 
    [NSBundle loadNibNamed: @"GSToolbarCustomizationPalette" owner: self];

  if(!_customizationPaletteIsRunning)
    {
      NSLog(@"Failed to load gorm for GSToolbarCustomizationPalette");
    }
}

- (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;
}

- (id) delegate
{
  return _delegate;
}

- (NSToolbarDisplayMode) displayMode
{
  return _displayMode;
}

- (NSString *) identifier
{
  return _identifier;
}

- (NSArray *) items
{ 
  return _items;
}

- (NSString *) selectedItemIdentifier
{
  return _selectedItemIdentifier;
}

- (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
{
  ASSIGN(_configurationDictionary, configDict);
}

/**
 * Sets the receivers delegate ... this is the object which will receive
 * -toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:
 * -toolbarAllowedItemIdentifiers: and -toolbarDefaultItemIdentifiers:
 * messages.
 */
 
- (void) setDelegate: (id)delegate
{ 
  [self _setDelegate: delegate broadcast: YES];
}

- (void) setSelectedItemIdentifier: (NSString *)itemIdentifier
{
  NSArray *selectedItems;
  NSArray *itemsToSelect;
  NSEnumerator *e;
  NSToolbarItem *item;
  NSArray *selectableIdentifiers = nil;
  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];
    }   
   
   if ([_delegate respondsToSelector:
     @selector(toolbarSelectableItemIdentifiers:)]) 
     {
       selectableIdentifiers = 
         [_delegate toolbarSelectableItemIdentifiers: self]; 
     }
   else
     {
       NSLog(@"toolbar delegate does not respond to %@", 
         @selector(toolbarSelectableItemIdentifiers:));
       return;
     }
   
   if (selectableIdentifiers == nil)
     {
       NSLog(@"toolbar delegate returns no such selectable item identifiers");
       return;
     }
   
   itemsToSelect = [_items objectsWithValue: itemIdentifier 
                                     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, itemIdentifier);
     }
   else
     {
       NSLog(@"toolbar delegate returns no such selectable item identifiers");
     }
}

- (NSToolbarSizeMode) sizeMode
{
  return _sizeMode;
}

- (BOOL) usesStandardBackgroundColor
{
  return [_toolbarView _usesStandardBackgroundColor];
}

- (void) setUsesStandardBackgroundColor: (BOOL)standard
{
  [_toolbarView _setUsesStandardBackgroundColor: standard];
}

// Private methods

- (void) _build
{
  /*
   * toolbar build :
   * will use the delegate when there is no toolbar model
   */
  
  GSToolbar *toolbarModel;
  NSArray *wantedItemIdentifiers;
  NSEnumerator *e;
  id itemIdentifier;
  int i = 0;
  
  _build = YES;

  RELEASE(_items);
  _items = [[NSMutableArray alloc] init];
    
  toolbarModel = [self _toolbarModel];

  if (toolbarModel != nil && [toolbarModel delegate] == _delegate)
    {
      wantedItemIdentifiers = 
        [[toolbarModel items] valueForKey: @"_itemIdentifier"];
    }
  else
    {
      wantedItemIdentifiers = [_delegate toolbarDefaultItemIdentifiers:self];
    }
    
  e = [wantedItemIdentifiers objectEnumerator];
  while ((itemIdentifier = [e nextObject]) != nil) 
    {
      [self _insertItemWithItemIdentifier: itemIdentifier 
                                  atIndex: i 
                                broadcast: NO];
      i++;
    }
  
  _build = NO;
}

- (void) _loadConfig
{
  if (_identifier != nil)
    { 
      NSUserDefaults     *defaults;
      NSString           *tableKey;
      id                  config;
      
      defaults  = [NSUserDefaults standardUserDefaults];
      tableKey = 
        [NSString stringWithFormat: @"GSToolbar Config %@",_identifier];

      config = [defaults objectForKey: tableKey];
      
      if (config != nil) 
	{
	  [self setConfigurationFromDictionary: config];
	}
    }
}

- (GSToolbar *) _toolbarModel
{
  NSArray *linked;
  id toolbar;

  linked = [toolbars objectsWithValue: [self identifier] 
                               forKey: @"_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 GSToolbar instance as a model
      if ([toolbar isMemberOfClass: [self class]] && toolbar != self)
        return toolbar;
    }
  
  return nil;
}

- (NSToolbarItem *) _toolbarItemForIdentifier: (NSString *)itemIdent
{
  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];
    }
    
  return item;
}


/*
 *
 * The methods below handles the toolbar edition and broacasts 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 = [[toolbars objectsWithValue: _identifier forKey: \
    @"_identifier"] objectEnumerator]; \
  GSToolbar *toolbar; \
  \
  while ((toolbar = [e nextObject]) != nil) \
    { \
      if (toolbar != self && [toolbar isMemberOfClass: [self class]]) \
        [toolbar signature]; \
    }

- (void) _insertItemWithItemIdentifier: (NSString *)itemIdentifier 
                               atIndex: (int)index 
                             broadcast: (BOOL)broadcast
{
  NSToolbarItem *item = nil;
  NSArray *allowedItems = [_delegate toolbarAllowedItemIdentifiers: self];
  
  if([allowedItems containsObject: itemIdentifier])
    {
      item = [self _toolbarItemForIdentifier: itemIdentifier];
      if (item == nil)
	{
	  item = 
            [_delegate toolbar: self itemForItemIdentifier: itemIdentifier
			         willBeInsertedIntoToolbar: YES];
	}
      
      if (item != nil)
        {
          NSArray *selectableItems;
	  
	  if ([_delegate respondsToSelector:
	    @selector(toolbarSelectableItemIdentifiers:)]) 
	    {
	      selectableItems = 
	        [_delegate toolbarSelectableItemIdentifiers: self]; 
	      if ([selectableItems containsObject: itemIdentifier])
	        [item _setSelectable: YES];
	    }
	  
	  [nc postNotificationName: NSToolbarWillAddItemNotification 
                            object: self];
          [item _setToolbar: self];
          [_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
{
  
  [_items removeObjectAtIndex: index];
  [_toolbarView _reload];
  [nc postNotificationName: NSToolbarDidRemoveItemNotification object: self];

  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) _setDelegate: (id)delegate broadcast: (BOOL)broadcast
{   
  
  if (_delegate == delegate 
    || (broadcast == NO && [_delegate isMemberOfClass: [delegate class]]))
    return;
  /* We don't reload instances which received this message and already have a
   * delegate based on a class identical to the parameter delegate, it permits
   * to use only one nib owner class as a toolbar delegate even if a new
   * instance of the nib owner are created with each new window (see
   * MiniController.m in the toolbar example application).
   */
    
  
  #define CHECK_REQUIRED_METHOD(selector_name) \
  if (![delegate respondsToSelector: @selector(selector_name)]) \
    [NSException raise: NSInternalInconsistencyException \
                format: @"delegate does not respond to %@",@#selector_name]

  CHECK_REQUIRED_METHOD(toolbar:itemForItemIdentifier:
    willBeInsertedIntoToolbar:); 
  CHECK_REQUIRED_METHOD(toolbarAllowedItemIdentifiers:);
  CHECK_REQUIRED_METHOD(toolbarDefaultItemIdentifiers:);

  if (_delegate) 
    [nc removeObserver: _delegate name: nil object: self];

  ASSIGN(_delegate, delegate);

  #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];
  if (_toolbarView != nil)
    [_toolbarView _reload];
  
  // broadcast now...
    
  if (broadcast) 
    {
      TRANSMIT(_setDelegate: _delegate broadcast: NO);
    } 
}

// Private Accessors

- (void) _setToolbarView: (GSToolbarView *)toolbarView
{
  GSValidationObject *validationObject = nil;
  GSValidationCenter *vc = [GSValidationCenter sharedValidationCenter];
  GSToolbar *toolbarModel = [self _toolbarModel];
  
  if (_toolbarView != nil)
    {
      validationObject = [vc validationObjectWithView: _toolbarView];
      if (validationObject != nil)
        {
          [validationObject removeObserver: self];
          if ([[validationObject observers] count] == 0)
            [vc unregisterValidationObject: validationObject];
        }
    }
    
  ASSIGN(_toolbarView, toolbarView);
  
  if (_toolbarView == nil)
    return;
        
  validationObject = [vc validationObjectWithView: _toolbarView];
  if (validationObject != nil)
   [validationObject addObserver: self];
   
  /* In the case, the user hasn't set a delegate until now, we set it.
   * Why ?
   * We don't set it before when the toolbar is inited, to do only one toolbar
   * content load.
   * ...
   * 1 toolbar = [[GSToolbar alloc] initWithIdentifier: @"blabla"];
   * 2 [toolbar setDelegate: myDelegate];
   * In case such method like 1 sets a default delegate for the identifier by
   * requesting a toolbar model, a toolbar content load would occur.
   * With a method like 2 which follows immediatly :
   * Another toolbar load content would occur related to a probably different
   * delegate. 
   */    
  if (_delegate == nil)
   [self _setDelegate: [toolbarModel delegate] broadcast: NO];
}

- (GSToolbarView *) _toolbarView 
{
  return _toolbarView;
}

- (void) _validate
{
  [self validateVisibleItems];
}

@end
