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