1/** <title>NSMenu</title>
2
3   Copyright (C) 1999,2016 Free Software Foundation, Inc.
4
5   Author: Fred Kiefer <FredKiefer@gmx.de>
6   Date: Aug 2001
7   Author: David Lazaro Saz <khelekir@encomix.es>
8   Date: Oct 1999
9   Author: Michael Hanni <mhanni@sprintmail.com>
10   Date: 1999
11   Author: Felipe A. Rodriguez <far@ix.netcom.com>
12   Date: July 1998
13   and:
14   Author: Ovidiu Predescu <ovidiu@net-community.com>
15   Date: May 1997
16
17   This file is part of the GNUstep GUI Library.
18
19   This library is free software; you can redistribute it and/or
20   modify it under the terms of the GNU Lesser General Public
21   License as published by the Free Software Foundation; either
22   version 2 of the License, or (at your option) any later version.
23
24   This library is distributed in the hope that it will be useful,
25   but WITHOUT ANY WARRANTY; without even the implied warranty of
26   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
27   Lesser General Public License for more details.
28
29   You should have received a copy of the GNU Lesser General Public
30   License along with this library; see the file COPYING.LIB.
31   If not, see <http://www.gnu.org/licenses/> or write to the
32   Free Software Foundation, 51 Franklin Street, Fifth Floor,
33   Boston, MA 02110-1301, USA.
34*/
35
36#import "config.h"
37#import <Foundation/NSCoder.h>
38#import <Foundation/NSArray.h>
39#import <Foundation/NSCharacterSet.h>
40#import <Foundation/NSDebug.h>
41#import <Foundation/NSException.h>
42#import <Foundation/NSProcessInfo.h>
43#import <Foundation/NSString.h>
44#import <Foundation/NSNotification.h>
45#import <Foundation/NSNotificationQueue.h>
46#import <Foundation/NSRunLoop.h>
47#import <Foundation/NSUserDefaults.h>
48#import <Foundation/NSValue.h>
49
50#import "AppKit/NSMatrix.h"
51#import "AppKit/NSApplication.h"
52#import "AppKit/NSCursor.h"
53#import "AppKit/NSWindow.h"
54#import "AppKit/NSEvent.h"
55#import "AppKit/NSFont.h"
56#import "AppKit/NSImage.h"
57#import "AppKit/NSMenu.h"
58#import "AppKit/NSMenuItem.h"
59#import "AppKit/NSMenuView.h"
60#import "AppKit/NSPanel.h"
61#import "AppKit/NSPopUpButton.h"
62#import "AppKit/NSPopUpButtonCell.h"
63#import "AppKit/NSScreen.h"
64#import "AppKit/NSAttributedString.h"
65
66#import "GSGuiPrivate.h"
67#import "NSDocumentFrameworkPrivate.h"
68#import "GNUstepGUI/GSTheme.h"
69
70/*
71  Drawing related:
72
73  NSMenu superMenu   (if not root menu, the parent meu)
74    ^
75    |
76    |    +------------------> NSMenuView view  (content, draws the menu items)
77    |    |
78  NSMenu +----------+-------> NSMenuPanel A    (regular window, torn off window)
79    |    |          `-------> NSMenuPanel B    (transient window)
80    |    |
81    |    +------------------> NSString title   (title)
82    |
83    v
84  NSMenu attachedMenu  (the menu attached to this one, during navigation)
85
86
87
88   +--[NSMenuPanel]------+
89   | +-[NSMenuView]----+ |
90   | | title if applic | |
91   | | +-------------+ | |
92   | | | NSMenuItem- | | |
93   | | | Cell        | | |
94   | | +-------------+ | |
95   | |       .         | |
96   | |       .         | |
97   | +-----------------+ |
98   +---------------------+
99
100   The two windows
101   ---------------
102
103   Basically we have for a menu two windows, window A and window B.
104   Window A is the regular window and Window B is used for transient windows.
105
106   At any one time, the views, like title view, NSMenuView are put either in
107   window A or in window B.  They are moved over from one window to the oter
108   when needed.
109
110   the code is supposed to know when it is using window A or B.
111   But it will probably only work correctly when
112
113   window A correspond to transient == NO
114   window B correspond to transient == YES
115*/
116
117
118/* Subclass of NSPanel since menus cannot become key */
119@interface NSMenuPanel : NSPanel
120{
121  NSMenu *_the_menu;
122}
123- (void) _setmenu: (NSMenu *)menu;
124@end
125
126@interface NSMenuView (GNUstepPrivate)
127- (NSArray *)_itemCells;
128@end
129
130
131static NSZone	*menuZone = NULL;
132static NSString	*NSMenuLocationsKey = @"NSMenuLocations";
133static NSString *NSEnqueuedMenuMoveName = @"EnqueuedMoveNotificationName";
134static NSNotificationCenter *nc;
135static BOOL menuBarVisible = YES;
136
137@interface	NSMenu (GNUstepPrivate)
138
139- (NSString *) _name;
140- (void) _setName: (NSString *)name;
141- (NSMenuPanel *) _createWindow;
142- (NSString *) _locationKey;
143- (void) _rightMouseDisplay: (NSEvent*)theEvent;
144- (void) _setGeometry;
145- (void) _updateUserDefaults: (id) notification;
146- (void) _organizeMenu;
147- (BOOL) _isVisible;
148- (BOOL) _isMain;
149
150@end
151
152
153@implementation NSMenuPanel
154- (void) _setmenu: (NSMenu *)menu
155{
156  _the_menu = menu;
157}
158
159- (BOOL) canBecomeKeyWindow
160{
161  /* See [NSWindow-_lossOfKeyOrMainWindow] */
162  return [_the_menu _isMain];
163}
164
165- (void) orderFrontRegardless
166{
167  NSInterfaceStyle style = NSInterfaceStyleForKey(@"NSMenuInterfaceStyle", nil);
168  if (style == NSWindows95InterfaceStyle)
169    {
170      // if we're the top level menu in Windows mode, don't show it.
171      if([_the_menu supermenu] == nil && [_the_menu _ownedByPopUp] == NO)
172	{
173	  return;
174	}
175    }
176  [super orderFrontRegardless];
177}
178@end
179
180@implementation	NSMenu (GNUstepPrivate)
181
182- (NSString *) _name;
183{
184  return _name;
185}
186
187- (void) _setName: (NSString *)aName
188{
189  ASSIGNCOPY(_name, aName);
190}
191
192- (NSString*) _locationKey
193{
194  NSInterfaceStyle style = NSInterfaceStyleForKey(@"NSMenuInterfaceStyle", nil);
195  if (style == NSMacintoshInterfaceStyle
196    || style == NSWindows95InterfaceStyle)
197    {
198      return nil;
199    }
200  if (_superMenu == nil)
201    {
202      if ([self _isMain])
203	{
204	  return @"\033";	/* Root menu.	*/
205	}
206      else
207	{
208	  return nil;		/* Unused menu.	*/
209	}
210    }
211  else if (_superMenu->_superMenu == nil)
212    {
213      return [NSString stringWithFormat: @"\033%@", [self title]];
214    }
215  else
216    {
217      return [[_superMenu _locationKey] stringByAppendingFormat: @"\033%@",
218	[self title]];
219    }
220}
221
222/* Create a non autorelease window for this menu.  */
223- (NSMenuPanel*) _createWindow
224{
225  NSMenuPanel *win = [[NSMenuPanel alloc]
226		     initWithContentRect: NSZeroRect
227		     styleMask: NSBorderlessWindowMask
228		     backing: NSBackingStoreBuffered
229		     defer: YES];
230  [win setBackgroundColor: [NSColor clearColor]];
231  [win setLevel: NSSubmenuWindowLevel];
232  [win setWorksWhenModal: NO];
233  [win setBecomesKeyOnlyIfNeeded: YES];
234  [win _setmenu: self];
235
236  return win;
237}
238
239/**
240   Will track the mouse movement.  It will trigger the updating of the user
241   defaults in due time.
242*/
243- (void) _menuMoved: (id) notification
244{
245  NSNotification *resend;
246
247  resend = [NSNotification notificationWithName: NSEnqueuedMenuMoveName
248					 object: self];
249
250  [[NSNotificationQueue defaultQueue]
251    enqueueNotification: resend
252    postingStyle: NSPostASAP
253    coalesceMask: NSNotificationCoalescingOnSender
254    forModes:  [NSArray arrayWithObject: NSDefaultRunLoopMode]];
255}
256
257- (void) _organizeMenu
258{
259  NSString *infoString = _(@"Info");
260  NSString *servicesString = _(@"Services");
261  int i;
262
263  if ([self _isMain])
264    {
265      NSString *appTitle;
266      NSMenu *appMenu;
267      id <NSMenuItem> appItem;
268
269      appTitle = [[[NSBundle mainBundle] localizedInfoDictionary]
270                     objectForKey: @"ApplicationName"];
271      if (nil == appTitle)
272        {
273          appTitle = [[NSProcessInfo processInfo] processName];
274        }
275      appItem = [self itemWithTitle: appTitle];
276      appMenu = [appItem submenu];
277
278      if (_menu.horizontal == YES)
279        {
280          NSMutableArray *itemsToMove;
281
282          itemsToMove = [NSMutableArray new];
283
284          if (appMenu == nil)
285            {
286              [self insertItemWithTitle: appTitle
287                    action: NULL
288                    keyEquivalent: @""
289                    atIndex: 0];
290              appItem = [self itemAtIndex: 0];
291              appMenu = [NSMenu new];
292              [self setSubmenu: appMenu forItem: appItem];
293              RELEASE(appMenu);
294            }
295          else
296            {
297              int index = [self indexOfItem: appItem];
298
299              if (index != 0)
300                {
301                  RETAIN (appItem);
302                  [self removeItemAtIndex: index];
303                  [self insertItem: appItem atIndex: 0];
304                  RELEASE (appItem);
305                }
306            }
307
308	  if ([[GSTheme theme] menuShouldShowIcon])
309	    {
310              NSImage *ti;
311              float bar;
312	      ti = [[NSApp applicationIconImage] copy];
313	      if (ti == nil)
314		{
315		  ti = [[NSImage imageNamed: @"GNUstep"] copy];
316		}
317	      [ti setScalesWhenResized: YES];
318	      bar = [NSMenuView menuBarHeight] - 4;
319	      [ti setSize: NSMakeSize(bar, bar)];
320	      [appItem setImage: ti];
321	      RELEASE(ti);
322	    }
323
324          // Collect all simple items plus "Info" and "Services"
325          for (i = 1; i < [_items count]; i++)
326            {
327              NSMenuItem *anItem = [_items objectAtIndex: i];
328              NSString *title = [anItem title];
329              NSMenu *submenu = [anItem submenu];
330
331              if (submenu == nil)
332                {
333                  [itemsToMove addObject: anItem];
334                }
335              else
336                {
337                  // The menu may not be localized, so we have to
338                  // check both the English and the local version.
339                  if ([title isEqual: @"Info"] ||
340                      [title isEqual: @"Services"] ||
341                      [title isEqual: infoString] ||
342                      [title isEqual: servicesString])
343                    {
344                      [itemsToMove addObject: anItem];
345                    }
346                }
347            }
348
349          for (i = 0; i < [itemsToMove count]; i++)
350            {
351              NSMenuItem *anItem = [itemsToMove objectAtIndex: i];
352
353              [self removeItem: anItem];
354              [appMenu addItem: anItem];
355            }
356
357          RELEASE(itemsToMove);
358        }
359      else
360        {
361          [appItem setImage: nil];
362          if (appMenu != nil)
363            {
364              NSArray	*array = [NSArray arrayWithArray: [appMenu itemArray]];
365              /*
366               * Everything above the Serives menu goes into the info submenu,
367               * the rest into the main menu.
368               */
369              int k = [appMenu indexOfItemWithTitle: servicesString];
370
371              // The menu may not be localized, so we have to
372              // check both the English and the local version.
373              if (k == -1)
374                k = [appMenu indexOfItemWithTitle: @"Services"];
375
376              if ((k > 0) && ([[array objectAtIndex: k - 1] isSeparatorItem]))
377                k--;
378
379              if (k == 1)
380                {
381                  // Exactly one info item
382                  NSMenuItem *anItem = [array objectAtIndex: 0];
383
384                  [appMenu removeItem: anItem];
385                  [self insertItem: anItem atIndex: 0];
386                }
387              else if (k > 1)
388                {
389                  id <NSMenuItem> infoItem;
390                  NSMenu *infoMenu;
391
392                  // Multiple info items, add a submenu for them
393                  [self insertItemWithTitle: infoString
394                        action: NULL
395                        keyEquivalent: @""
396                        atIndex: 0];
397                  infoItem = [self itemAtIndex: 0];
398                  infoMenu = [NSMenu new];
399                  [self setSubmenu: infoMenu forItem: infoItem];
400                  RELEASE(infoMenu);
401
402                  for (i = 0; i < k; i++)
403                    {
404                      NSMenuItem *anItem = [array objectAtIndex: i];
405
406                      [appMenu removeItem: anItem];
407                      [infoMenu addItem: anItem];
408                    }
409                }
410              else
411                {
412                  // No service menu, or it is the first item.
413                  // We still look for an info item.
414                  NSMenuItem *anItem = [array objectAtIndex: 0];
415                  NSString *title = [anItem title];
416
417                  // The menu may not be localized, so we have to
418                  // check both the English and the local version.
419                  if ([title isEqual: @"Info"] ||
420                      [title isEqual: infoString])
421                    {
422                      [appMenu removeItem: anItem];
423                      [self insertItem: anItem atIndex: 0];
424                      k = 1;
425                    }
426                  else
427                    {
428                      k = 0;
429                    }
430                }
431
432              // Copy the remaining entries.
433              for (i = k; i < [array count]; i++)
434                {
435                  NSMenuItem *anItem = [array objectAtIndex: i];
436
437                  [appMenu removeItem: anItem];
438                  [self addItem: anItem];
439                }
440
441              [self removeItem: appItem];
442            }
443        }
444    }
445
446  // recurse over all submenus
447  for (i = 0; i < [_items count]; i++)
448    {
449      NSMenuItem *anItem = [_items objectAtIndex: i];
450      NSMenu *submenu = [anItem submenu];
451
452      if (submenu != nil)
453        {
454          if ([submenu isTransient])
455            {
456              [submenu closeTransient];
457            }
458          [submenu close];
459          [submenu _organizeMenu];
460        }
461    }
462
463  [[self menuRepresentation] update];
464  [self sizeToFit];
465}
466
467- (void) _setGeometry
468{
469  NSPoint        origin;
470
471  if (_menu.horizontal == YES)
472    {
473      NSRect screenFrame = [[NSScreen mainScreen] frame];
474      origin = NSMakePoint (0, screenFrame.size.height
475                            - [_aWindow frame].size.height);
476      origin.y += screenFrame.origin.y;
477      [_aWindow setFrameOrigin: origin];
478      [_bWindow setFrameOrigin: origin];
479    }
480  else
481    {
482      NSString       *key;
483
484      if (nil != (key = [self _locationKey]))
485	{
486	  NSUserDefaults *defaults;
487	  NSDictionary   *menuLocations;
488	  NSString       *location;
489
490	  defaults = [NSUserDefaults standardUserDefaults];
491	  menuLocations = [defaults objectForKey: NSMenuLocationsKey];
492
493	  if ([menuLocations isKindOfClass: [NSDictionary class]])
494	    location = [menuLocations objectForKey: key];
495	  else
496	    location = nil;
497
498	  if (location && [location isKindOfClass: [NSString class]])
499	    {
500	      [_aWindow setFrameFromString: location];
501	      [_bWindow setFrameFromString: location];
502	      return;
503	    }
504	}
505
506      if ((_aWindow != nil) && ([_aWindow screen] != nil))
507        {
508          origin = NSMakePoint(0, [[_aWindow screen] visibleFrame].size.height
509                               - [_aWindow frame].size.height);
510
511          [_aWindow setFrameOrigin: origin];
512          [_bWindow setFrameOrigin: origin];
513        }
514    }
515}
516
517/**
518   Save the current menu position in the standard user defaults
519*/
520- (void) _updateUserDefaults: (id) notification
521{
522  if (_menu.horizontal == NO)
523    {
524      NSString *key;
525
526      NSDebugLLog (@"NSMenu", @"Synchronizing user defaults");
527      key = [self _locationKey];
528      if (key != nil)
529        {
530          NSUserDefaults	*defaults;
531          NSMutableDictionary	*menuLocations;
532          NSString		*locString;
533
534          defaults = [NSUserDefaults standardUserDefaults];
535          menuLocations = [defaults objectForKey: NSMenuLocationsKey];
536          if ([menuLocations isKindOfClass: [NSDictionary class]])
537            menuLocations = AUTORELEASE([menuLocations mutableCopy]);
538          else
539            menuLocations = nil;
540
541          if ([_aWindow isVisible]
542              && ([self isTornOff] || ([self _isMain])))
543            {
544              if (menuLocations == nil)
545                {
546                  menuLocations = AUTORELEASE([[NSMutableDictionary alloc]
547                                                  initWithCapacity: 2]);
548                }
549              locString = [[self window] stringWithSavedFrame];
550              [menuLocations setObject: locString forKey: key];
551            }
552          else
553            {
554              [menuLocations removeObjectForKey: key];
555            }
556
557          if ([menuLocations count] > 0)
558            {
559              [defaults setObject: menuLocations
560                        forKey: NSMenuLocationsKey];
561            }
562          else
563            {
564              [defaults removeObjectForKey: NSMenuLocationsKey];
565            }
566          [defaults synchronize];
567        }
568    }
569}
570
571- (void) _rightMouseDisplay: (NSEvent*)theEvent
572{
573  [[GSTheme theme] rightMouseDisplay: self
574			    forEvent: theEvent];
575}
576
577- (BOOL) _isVisible
578{
579  return [_aWindow isVisible] || [_bWindow isVisible];
580}
581
582- (BOOL) _isMain
583{
584  return [NSApp mainMenu] == self;
585}
586
587@end
588
589
590@implementation NSMenu
591
592/*
593 * Class Methods
594 */
595+ (void) initialize
596{
597  if (self == [NSMenu class])
598    {
599      [self setVersion: 1];
600      nc = [NSNotificationCenter defaultCenter];
601    }
602}
603
604+ (void) setMenuZone: (NSZone*)zone
605{
606  menuZone = zone;
607}
608
609+ (NSZone*) menuZone
610{
611  return menuZone;
612}
613
614+ (BOOL) menuBarVisible
615{
616  return menuBarVisible;
617}
618
619+ (void) setMenuBarVisible: (BOOL)flag
620{
621  menuBarVisible = flag;
622}
623
624/*
625 *
626 */
627- (id) init
628{
629  return [self initWithTitle: [[NSProcessInfo processInfo] processName]];
630}
631
632- (void) dealloc
633{
634  [nc removeObserver: self];
635
636  // Now clean the pointer to us stored each _items element
637  [_items makeObjectsPerformSelector: @selector(setMenu:) withObject: nil];
638
639  RELEASE(_notifications);
640  RELEASE(_title);
641  RELEASE(_items);
642  [_view setMenu: nil];
643  RELEASE(_view);
644  RELEASE(_aWindow);
645  RELEASE(_bWindow);
646  RELEASE(_name);
647
648  [super dealloc];
649}
650
651/*
652
653*/
654- (id) initWithTitle: (NSString*)aTitle
655{
656  NSMenuView *menuRep;
657
658  self = [super init];
659  if (!self)
660    return nil;
661
662  // Keep the title.
663  ASSIGN(_title, aTitle);
664
665  // Create an array to store our menu items.
666  _items = [[NSMutableArray alloc] init];
667
668  _menu.changedMessagesEnabled = YES;
669  _notifications = [[NSMutableArray alloc] init];
670  _menu.needsSizing = YES;
671  // According to the spec, menus do autoenable by default.
672  _menu.autoenable = YES;
673
674
675  /* Please note that we own all this menu network of objects.  So,
676     none of these objects should be retaining us.  When we are deallocated,
677     we release all the objects we own, and that should cause deallocation
678     of the whole menu network.  */
679
680  // Create the windows that will display the menu.
681  _aWindow = [self _createWindow];
682  _bWindow = [self _createWindow];
683  [_bWindow setLevel: NSPopUpMenuWindowLevel];
684
685  // Create a NSMenuView to draw our menu items.
686  menuRep = [[NSMenuView alloc] initWithFrame: NSZeroRect];
687  [self setMenuRepresentation: menuRep];
688  RELEASE(menuRep);
689
690  /* Set up the notification to start the process of redisplaying
691     the menus where the user left them the last time.
692
693     Use NSApplicationDidFinishLaunching, and not
694     NSApplicationWillFinishLaunching, so that the programmer can set
695     up menus in NSApplicationWillFinishLaunching.
696  */
697  [nc addObserver: self
698      selector: @selector(applicationDidFinishLaunching:)
699      name: NSApplicationDidFinishLaunchingNotification
700      object: NSApp];
701
702  [nc addObserver: self
703      selector: @selector(_showOnActivateApp:)
704      name: NSApplicationWillBecomeActiveNotification
705      object: NSApp];
706
707  [nc addObserver: self
708      selector: @selector (_menuMoved:)
709      name: NSWindowDidMoveNotification
710      object: _aWindow];
711
712  [nc addObserver: self
713      selector: @selector (_updateUserDefaults:)
714      name: NSEnqueuedMenuMoveName
715      object: self];
716
717  return self;
718}
719
720/*
721 * Setting Up the Menu Commands
722 */
723
724 - (void) menuChanged
725{
726  // propagate notification up to the main menu
727  if ([self _isMain])
728    _menu.mainMenuChanged = YES;
729  else
730    [[self supermenu] menuChanged];
731}
732
733- (void) insertItem: (id <NSMenuItem>)newItem
734	    atIndex: (NSInteger)index
735{
736  NSNotification *inserted;
737  NSDictionary   *d;
738
739  if (![(id)newItem conformsToProtocol: @protocol(NSMenuItem)])
740    {
741      NSLog(@"You must use an object that conforms to NSMenuItem.\n");
742      return;
743    }
744
745  /*
746   * If the item is already attached to another menu it
747   * isn't added.
748   */
749  if ([newItem menu] != nil)
750    {
751      NSLog(@"The object %@ is already attached to a menu, then it isn't possible to add it.\n", newItem);
752      return;
753    }
754
755  [_items insertObject: newItem atIndex: index];
756  _menu.needsSizing = YES;
757  [(NSMenuView*)_view setNeedsSizing: YES];
758
759  // Create the notification for the menu representation.
760  d = [NSDictionary
761	  dictionaryWithObject: [NSNumber numberWithInteger: index]
762	  forKey: @"NSMenuItemIndex"];
763  inserted = [NSNotification
764		 notificationWithName: NSMenuDidAddItemNotification
765		 object: self
766		 userInfo: d];
767
768  if (_menu.changedMessagesEnabled)
769    [nc postNotification: inserted];
770  else
771    [_notifications addObject: inserted];
772  [self menuChanged];
773
774  // Set this after the insert notification has been sent.
775  [newItem setMenu: self];
776}
777
778- (id <NSMenuItem>) insertItemWithTitle: (NSString*)aString
779			         action: (SEL)aSelector
780			  keyEquivalent: (NSString*)charCode
781			        atIndex: (NSInteger)index
782{
783  NSMenuItem *anItem = [[NSMenuItem alloc] initWithTitle: aString
784					   action: aSelector
785					   keyEquivalent: charCode];
786
787  // Insert the new item into the menu.
788  [self insertItem: anItem atIndex: index];
789
790  // For returns sake.
791  return AUTORELEASE(anItem);
792}
793
794- (void) addItem: (id <NSMenuItem>)newItem
795{
796  [self insertItem: newItem atIndex: [_items count]];
797}
798
799- (id <NSMenuItem>) addItemWithTitle: (NSString*)aString
800			      action: (SEL)aSelector
801		       keyEquivalent: (NSString*)keyEquiv
802{
803  return [self insertItemWithTitle: aString
804			    action: aSelector
805		     keyEquivalent: keyEquiv
806			   atIndex: [_items count]];
807}
808
809- (void) removeItem: (id <NSMenuItem>)anItem
810{
811  NSInteger index = [self indexOfItem: anItem];
812
813  if (-1 == index)
814    return;
815
816  [self removeItemAtIndex: index];
817}
818
819- (void) removeItemAtIndex: (NSInteger)index
820{
821  NSNotification *removed;
822  NSDictionary	 *d;
823  id		 anItem = [_items objectAtIndex: index];
824
825  if (!anItem)
826    return;
827
828  [anItem setMenu: nil];
829  [_items removeObjectAtIndex: index];
830  _menu.needsSizing = YES;
831  [(NSMenuView*)_view setNeedsSizing: YES];
832
833  d = [NSDictionary dictionaryWithObject: [NSNumber numberWithInteger: index]
834		    forKey: @"NSMenuItemIndex"];
835  removed = [NSNotification
836		notificationWithName: NSMenuDidRemoveItemNotification
837		object: self
838		userInfo: d];
839
840  if (_menu.changedMessagesEnabled)
841    [nc postNotification: removed];
842  else
843    [_notifications addObject: removed];
844  [self menuChanged];
845}
846
847- (void) itemChanged: (id <NSMenuItem>)anObject
848{
849  NSNotification *changed;
850  NSDictionary   *d;
851  NSInteger index = [self indexOfItem: anObject];
852
853  if (-1 == index)
854    return;
855
856  _menu.needsSizing = YES;
857  [(NSMenuView*)_view setNeedsSizing: YES];
858
859  d = [NSDictionary dictionaryWithObject: [NSNumber numberWithInteger: index]
860		    forKey: @"NSMenuItemIndex"];
861  changed = [NSNotification
862	      notificationWithName: NSMenuDidChangeItemNotification
863	                    object: self
864	                  userInfo: d];
865
866  if (_menu.changedMessagesEnabled)
867    [nc postNotification: changed];
868  else
869    [_notifications addObject: changed];
870  [self menuChanged];
871
872  // Update the menu.
873  [self update];
874}
875
876/*
877 * Finding Menu Items
878 */
879- (id <NSMenuItem>) itemWithTag: (NSInteger)aTag
880{
881  NSUInteger i;
882  NSUInteger count = [_items count];
883
884  for (i = 0; i < count; i++)
885    {
886      id menuItem = [_items objectAtIndex: i];
887
888      if ([menuItem tag] == aTag)
889        return menuItem;
890    }
891  return nil;
892}
893
894- (id <NSMenuItem>) itemWithTitle: (NSString*)aString
895{
896  NSUInteger i;
897  NSUInteger count = [_items count];
898
899  for (i = 0; i < count; i++)
900    {
901      id menuItem = [_items objectAtIndex: i];
902
903      if ([[menuItem title] isEqualToString: aString])
904        return menuItem;
905    }
906  return nil;
907}
908
909- (NSMenuItem *) itemAtIndex: (NSInteger)index
910{
911  if (index >= [_items count] || index < 0)
912    [NSException  raise: NSRangeException
913		 format: @"Range error in method -itemAtIndex: "];
914
915  return [_items objectAtIndex: index];
916}
917
918- (NSInteger) numberOfItems
919{
920  return [_items count];
921}
922
923- (NSArray*) itemArray
924{
925  return (NSArray*)_items;
926}
927
928/*
929 * Finding Indices of Menu Items
930 */
931- (NSInteger) indexOfItem: (id <NSMenuItem>)anObject
932{
933  NSUInteger index;
934
935  index = [_items indexOfObjectIdenticalTo: anObject];
936
937  if (index == NSNotFound)
938    return -1;
939  else
940    return index;
941}
942
943- (NSInteger) indexOfItemWithTitle: (NSString*)aTitle
944{
945  id anItem;
946
947  if ((anItem = [self itemWithTitle: aTitle]))
948    return [_items indexOfObjectIdenticalTo: anItem];
949  else
950    return -1;
951}
952
953- (NSInteger) indexOfItemWithTag: (NSInteger)aTag
954{
955  id anItem;
956
957  if ((anItem = [self itemWithTag: aTag]))
958    return [_items indexOfObjectIdenticalTo: anItem];
959  else
960    return -1;
961}
962
963- (NSInteger) indexOfItemWithTarget: (id)anObject
964		    andAction: (SEL)actionSelector
965{
966  NSUInteger i;
967  NSUInteger count = [_items count];
968
969  for (i = 0; i < count; i++)
970    {
971      NSMenuItem *menuItem = [_items objectAtIndex: i];
972
973      if (actionSelector == 0 || sel_isEqual([menuItem action], actionSelector))
974        {
975          // There are different possibilities to implement the check here
976          if ([menuItem target] == anObject)
977            {
978              return i;
979            }
980        }
981    }
982
983  return -1;
984}
985
986- (NSInteger) indexOfItemWithRepresentedObject: (id)anObject
987{
988  NSUInteger i;
989  NSUInteger count = [_items count];
990
991  for (i = 0; i < count; i++)
992    {
993      if ([[[_items objectAtIndex: i] representedObject]
994              isEqual: anObject])
995        {
996          return i;
997        }
998    }
999
1000  return -1;
1001}
1002
1003- (NSInteger) indexOfItemWithSubmenu: (NSMenu *)anObject
1004{
1005  NSUInteger i;
1006  NSUInteger count = [_items count];
1007
1008  for (i = 0; i < count; i++)
1009    {
1010      id item = [_items objectAtIndex: i];
1011
1012      if ([item hasSubmenu] &&
1013          [[item submenu] isEqual: anObject])
1014        {
1015          return i;
1016        }
1017    }
1018
1019  return -1;
1020}
1021
1022//
1023// Managing Submenus.
1024//
1025- (void) setSubmenu: (NSMenu *)aMenu
1026	    forItem: (id <NSMenuItem>)anItem
1027{
1028  [anItem setSubmenu: aMenu];
1029}
1030
1031- (void) submenuAction: (id)sender
1032{
1033}
1034
1035
1036- (NSMenu *) attachedMenu
1037{
1038  if (_attachedMenu && _menu.transient
1039      && !_attachedMenu->_menu.transient)
1040    return nil;
1041
1042  return _attachedMenu;
1043}
1044
1045
1046/**
1047   Look for the semantics in the header.  Note that
1048   this implementation works because there are ... cases:
1049   <enum>
1050   <item>
1051   This menu is transient, its supermenu is also transient.
1052   In this case we just do the check between the transient windows
1053   and everything is fine
1054   </item>
1055   <item>
1056   The menu is transient, its supermenu is not transient.
1057   This can go WRONG
1058   </item>
1059   </enum>
1060*/
1061- (BOOL) isAttached
1062{
1063  return _superMenu && [_superMenu attachedMenu] == self;
1064}
1065
1066- (BOOL) isTornOff
1067{
1068  return _menu.is_tornoff;
1069}
1070
1071- (NSPoint) locationForSubmenu: (NSMenu*)aSubmenu
1072{
1073  return [_view locationForSubmenu: aSubmenu];
1074}
1075
1076- (NSMenu *) supermenu
1077{
1078  return _superMenu;
1079}
1080
1081- (void) setSupermenu: (NSMenu *)supermenu
1082{
1083  /* The supermenu retains us (indirectly).  Do not retain it.  */
1084  _superMenu = supermenu;
1085}
1086
1087//
1088// Enabling and Disabling Menu Items
1089//
1090- (void) setAutoenablesItems: (BOOL)flag
1091{
1092  _menu.autoenable = flag;
1093}
1094
1095- (BOOL) autoenablesItems
1096{
1097  return _menu.autoenable;
1098}
1099
1100- (void) _updateSubmenu
1101{
1102  if ([self _isVisible])
1103    {
1104      // Update the menu items when the menu is visible...
1105      [self update];
1106    }
1107  else
1108    {
1109      // ...else only progress to submenus
1110      NSUInteger i;
1111      NSUInteger count = [_items count];
1112
1113      for (i = 0; i < count; i++)
1114        {
1115          NSMenuItem *item = [_items objectAtIndex: i];
1116
1117          if ([item hasSubmenu])
1118            {
1119              [[item submenu] _updateSubmenu];
1120            }
1121        }
1122    }
1123}
1124
1125- (void) _updateMenuWithDelegate
1126{
1127  if ([_delegate respondsToSelector: @selector(menuNeedsUpdate:)])
1128    {
1129      [_delegate menuNeedsUpdate: self];
1130    }
1131  else if ([_delegate respondsToSelector: @selector(numberOfItemsInMenu:)])
1132    {
1133      NSInteger num;
1134
1135      num = [_delegate numberOfItemsInMenu: self];
1136      if (num > 0)
1137        {
1138          BOOL cont = YES;
1139          NSInteger i = 0;
1140          NSInteger curr = [self numberOfItems];
1141
1142          while (num < curr)
1143            {
1144              [self removeItemAtIndex: --curr];
1145            }
1146          while (num > curr)
1147            {
1148              [self insertItemWithTitle: @""
1149                                 action: NULL
1150                          keyEquivalent: @""
1151                                atIndex: curr++];
1152            }
1153
1154          // FIXME: Should only process the items we display
1155          while (cont && i < num)
1156            {
1157              cont = [_delegate menu: self
1158                          updateItem: (NSMenuItem*)[self itemAtIndex: i]
1159                             atIndex: i
1160                        shouldCancel: NO];
1161              i++;
1162            }
1163        }
1164    }
1165}
1166
1167- (void) _autoenableItem: (NSMenuItem*)item
1168{
1169  SEL	      action = [item action];
1170  id	      validator = nil;
1171  BOOL	      wasEnabled = [item isEnabled];
1172  BOOL	      shouldBeEnabled;
1173
1174  // If there is no action - there can be no validator for the item.
1175  if (action)
1176    {
1177      validator = [NSApp targetForAction: action
1178                                      to: [item target]
1179                                    from: item];
1180    }
1181  else if (_popUpButtonCell != nil)
1182    {
1183      if (NULL != (action = [_popUpButtonCell action]))
1184        {
1185          validator = [NSApp targetForAction: action
1186                                          to: [_popUpButtonCell target]
1187                                        from: [_popUpButtonCell controlView]];
1188        }
1189    }
1190
1191  if (validator == nil)
1192    {
1193      if ((action == NULL) && (_popUpButtonCell != nil))
1194        {
1195          shouldBeEnabled = YES;
1196        }
1197      else
1198        {
1199          shouldBeEnabled = NO;
1200        }
1201    }
1202  else if ([validator respondsToSelector: @selector(validateMenuItem:)])
1203    {
1204      shouldBeEnabled = [validator validateMenuItem: item];
1205    }
1206  else if ([validator respondsToSelector: @selector(validateUserInterfaceItem:)])
1207    {
1208      shouldBeEnabled = [validator validateUserInterfaceItem: item];
1209    }
1210  else if ([item hasSubmenu] && [[item submenu] numberOfItems] == 0)
1211    {
1212      shouldBeEnabled = NO;
1213    }
1214  else
1215    {
1216      shouldBeEnabled = YES;
1217    }
1218
1219  if (shouldBeEnabled != wasEnabled)
1220    {
1221      [item setEnabled: shouldBeEnabled];
1222    }
1223}
1224
1225- (void) update
1226{
1227  if (_delegate)
1228    {
1229      [self _updateMenuWithDelegate];
1230    }
1231
1232  // We use this as a recursion check.
1233  if (!_menu.changedMessagesEnabled)
1234    return;
1235
1236  if ([self autoenablesItems])
1237    {
1238      NSUInteger i;
1239      NSUInteger count = [_items count];
1240
1241      // Temporary disable automatic displaying of menu.
1242      [self setMenuChangedMessagesEnabled: NO];
1243
1244      NS_DURING
1245	{
1246	  for (i = 0; i < count; i++)
1247	    {
1248	      NSMenuItem *item = [_items objectAtIndex: i];
1249
1250	      if ([item hasSubmenu])
1251                {
1252                  [[item submenu] _updateSubmenu];
1253                }
1254
1255              [self _autoenableItem: item];
1256	    }
1257          }
1258	NS_HANDLER
1259	  {
1260	    NSLog(@"Error Occurred While Updating Menu %@: %@", [self title], localException);
1261	  }
1262	NS_ENDHANDLER
1263      // Reenable displaying of menus
1264      // this will send pending _notifications
1265      [self setMenuChangedMessagesEnabled: YES];
1266    }
1267
1268  if (_menu.mainMenuChanged)
1269    {
1270      if (NSInterfaceStyleForKey(@"NSMenuInterfaceStyle", nil) == NSWindows95InterfaceStyle)
1271        {
1272          [[GSTheme theme] updateAllWindowsWithMenu: self];
1273        }
1274      _menu.mainMenuChanged = NO;
1275    }
1276
1277  if (_menu.needsSizing && [self _isVisible])
1278    {
1279      NSDebugLLog (@"NSMenu", @" Calling Size To Fit (A)");
1280      [self sizeToFit];
1281    }
1282
1283  return;
1284}
1285
1286//
1287// Handling Keyboard Equivalents
1288//
1289- (BOOL) performKeyEquivalent: (NSEvent*)theEvent
1290{
1291  NSUInteger i;
1292  NSUInteger count = [_items count];
1293  NSEventType type = [theEvent type];
1294  NSUInteger modifiers = [theEvent modifierFlags];
1295  NSString *keyEquivalent = [theEvent charactersIgnoringModifiers];
1296  NSUInteger relevantModifiersMask = NSCommandKeyMask | NSAlternateKeyMask | NSControlKeyMask;
1297
1298  if ((type != NSKeyDown && type != NSKeyUp) || [keyEquivalent length] == 0)
1299    return NO;
1300
1301  /* Take shift key into account only for control keys and arrow and function keys */
1302  if ((modifiers & NSFunctionKeyMask)
1303      || ([keyEquivalent length] > 0 && [[NSCharacterSet controlCharacterSet] characterIsMember:[keyEquivalent characterAtIndex:0]]))
1304    relevantModifiersMask |= NSShiftKeyMask;
1305
1306  if (![self _isVisible] && _delegate)
1307    {
1308      // Need to enable items as the automatic mechanism is switched off for invisible menus
1309      [self update];
1310    }
1311
1312  for (i = 0; i < count; i++)
1313    {
1314      NSMenuItem *item = [_items objectAtIndex: i];
1315
1316      if ([item hasSubmenu])
1317        {
1318	  /* Ignore the Services submenu during menu traversal so that its key
1319	     equivalents do not accidentally shadow standard key equivalents
1320	     in the application's own menus. NSApp calls -performKeyEquivalent:
1321	     explicitly for the Services menu when no matching key equivalent
1322	     was found here (see NSApplication -sendEvent:).
1323	     Note: Shadowing is no problem for a standard OpenStep menu, where
1324	     the Services menu appears close to the end of the main menu, but
1325	     is very likely for Macintosh or Windows 95 interface styles, where
1326	     the Services menu appears in the first submenu of the main menu. */
1327	  // FIXME Should really remove conflicting key equivalents from the
1328	  // menus so that users don't get confused.
1329	  if ([item submenu] == [NSApp servicesMenu])
1330	    continue;
1331          // Recurse through submenus whether active or not.
1332          if ([[item submenu] performKeyEquivalent: theEvent])
1333            {
1334              // The event has been handled by an item in the submenu.
1335              return YES;
1336            }
1337        }
1338      else
1339        {
1340          NSUInteger mask = [item keyEquivalentModifierMask];
1341
1342          if ([[item keyEquivalent] isEqualToString: keyEquivalent]
1343            && (modifiers & relevantModifiersMask) == (mask & relevantModifiersMask))
1344            {
1345              if (![self _isVisible] && !_delegate)
1346                {
1347                  // Need to enable item as the automatic mechanism is switched off for invisible menus
1348                  [self _autoenableItem: item];
1349                }
1350              if ([item isEnabled])
1351                {
1352                  [_view performActionWithHighlightingForItemAtIndex: i];
1353                }
1354              return YES;
1355            }
1356        }
1357    }
1358  return NO;
1359}
1360
1361//
1362// Simulating Mouse Clicks
1363//
1364- (void)  performActionForItemAtIndex: (NSInteger)index
1365{
1366  id<NSMenuItem> item = [_items objectAtIndex: index];
1367  NSDictionary *d;
1368  SEL action;
1369
1370  if (![item isEnabled])
1371    return;
1372
1373  // Send the actual action and the stipulated notifications.
1374  d = [NSDictionary dictionaryWithObject: item forKey: @"MenuItem"];
1375  [nc postNotificationName: NSMenuWillSendActionNotification
1376                    object: self
1377                  userInfo: d];
1378
1379
1380  if (_popUpButtonCell != nil)
1381    {
1382      // Tell the popup button, which item was selected
1383      [_popUpButtonCell selectItemAtIndex: index];
1384    }
1385
1386  if ((action = [item action]) != NULL)
1387    {
1388      [NSApp sendAction: action
1389	     to: [item target]
1390	     from: item];
1391    }
1392  else if (_popUpButtonCell != nil)
1393    {
1394      if ((action = [_popUpButtonCell action]) != NULL)
1395	[NSApp sendAction: action
1396	       to: [_popUpButtonCell target]
1397	       from: [_popUpButtonCell controlView]];
1398    }
1399
1400  [nc postNotificationName: NSMenuDidSendActionNotification
1401                    object: self
1402                  userInfo: d];
1403}
1404
1405//
1406// Setting the Title
1407//
1408- (void) setTitle: (NSString*)aTitle
1409{
1410  ASSIGN(_title, aTitle);
1411
1412  _menu.needsSizing = YES;
1413  [(NSMenuView*)_view setNeedsSizing: YES];
1414  if ([self _isVisible])
1415    {
1416      [self sizeToFit];
1417    }
1418}
1419
1420- (NSString*) title
1421{
1422  return _title;
1423}
1424
1425- (id) delegate
1426{
1427  return _delegate;
1428}
1429
1430- (void) setDelegate: (id)delegate
1431{
1432  _delegate = delegate;
1433}
1434
1435- (float) menuBarHeight
1436{
1437  // FIXME
1438  return [NSMenuView menuBarHeight];
1439}
1440
1441//
1442// Setting the Representing Object
1443//
1444- (void) setMenuRepresentation: (id)menuRep
1445{
1446  NSView *contentView;
1447
1448  if (![menuRep isKindOfClass: [NSMenuView class]])
1449    {
1450      NSLog(@"You must use an NSMenuView, or a derivative thereof.\n");
1451      return;
1452    }
1453
1454  /* If we are replacing a menu representation with a new version,
1455   * we should display it in the same view as the old representation.
1456   * If we can't find a view for that, we display in the content view
1457   * of our default window.
1458   */
1459  if ([_view superview] == nil)
1460    {
1461      contentView = [_aWindow contentView];
1462    }
1463  else
1464    {
1465      contentView = [_view superview];
1466    }
1467
1468  if (_view == menuRep)
1469    {
1470      /* Hack ... if the representation was 'borrowed' for an in-window
1471       * menu, we will still have it recorded as ours, but it won't be
1472       * in our view hierarchy, so we have to re-add it.
1473       */
1474      /*
1475      if (contentView != [menuRep superview])
1476	{
1477          [contentView addSubview: menuRep];
1478	}
1479      */
1480      return;
1481    }
1482
1483  _menu.horizontal = [menuRep isHorizontal];
1484
1485  if (_view != nil)
1486    {
1487      // remove the old representation
1488      [_view removeFromSuperview];
1489      [_view setMenu: nil];
1490    }
1491
1492  ASSIGN(_view, menuRep);
1493  [_view setMenu: self];
1494
1495  // add the new representation
1496  [contentView addSubview: _view];
1497}
1498
1499- (id) menuRepresentation
1500{
1501  return _view;
1502}
1503
1504- (id) contextMenuRepresentation
1505{
1506  return nil;
1507}
1508
1509- (void) setContextMenuRepresentation: (id)representation
1510{
1511}
1512
1513- (id) tearOffMenuRepresentation
1514{
1515  return nil;
1516}
1517
1518- (void) setTearOffMenuRepresentation: (id)representation
1519{
1520}
1521
1522//
1523// Updating the Menu Layout
1524//
1525// Wim 20030301: Question, what happens when the notification trigger
1526// new notifications?  I think it is not allowed to add items
1527// to the _notifications array while enumerating it.
1528- (void) setMenuChangedMessagesEnabled: (BOOL)flag
1529{
1530  if (_menu.changedMessagesEnabled != flag)
1531    {
1532      if (flag)
1533	{
1534	  if ([_notifications count])
1535	    {
1536	      NSEnumerator *enumerator = [_notifications objectEnumerator];
1537	      id            aNotification;
1538
1539	      while ((aNotification = [enumerator nextObject]))
1540		[nc postNotification: aNotification];
1541	    }
1542
1543	  // Clean the notification array.
1544	  [_notifications removeAllObjects];
1545	}
1546
1547      _menu.changedMessagesEnabled = flag;
1548    }
1549}
1550
1551- (BOOL) menuChangedMessagesEnabled
1552{
1553  return _menu.changedMessagesEnabled;
1554}
1555
1556- (void) sizeToFit
1557{
1558  NSRect oldWindowFrame;
1559  NSRect newWindowFrame;
1560  NSRect menuFrame;
1561
1562  [_view sizeToFit];
1563
1564  menuFrame = [_view frame];
1565
1566  // Main
1567  oldWindowFrame = [_aWindow frame];
1568  newWindowFrame = [NSWindow frameRectForContentRect: menuFrame
1569                             styleMask: [_aWindow styleMask]];
1570
1571  if (oldWindowFrame.size.height > 1)
1572    {
1573      newWindowFrame.origin = NSMakePoint (oldWindowFrame.origin.x,
1574                                           oldWindowFrame.origin.y
1575                                           + oldWindowFrame.size.height
1576                                           - newWindowFrame.size.height);
1577    }
1578  [_aWindow setFrame: newWindowFrame display: NO];
1579
1580  // Transient
1581  oldWindowFrame = [_bWindow frame];
1582  newWindowFrame = [NSWindow frameRectForContentRect: menuFrame
1583                             styleMask: [_bWindow styleMask]];
1584  if (oldWindowFrame.size.height > 1)
1585    {
1586      newWindowFrame.origin = NSMakePoint (oldWindowFrame.origin.x,
1587                                           oldWindowFrame.origin.y
1588                                           + oldWindowFrame.size.height
1589                                           - newWindowFrame.size.height);
1590    }
1591  [_bWindow setFrame: newWindowFrame display: NO];
1592
1593  if (_popUpButtonCell == nil)
1594    {
1595      [_view setFrameOrigin: NSMakePoint (0, 0)];
1596    }
1597
1598  [_view setNeedsDisplay: YES];
1599
1600  _menu.needsSizing = NO;
1601}
1602
1603/*
1604 * Displaying Context Sensitive Help
1605 */
1606- (void) helpRequested: (NSEvent *)event
1607{
1608  // TODO: Won't be implemented until we have NSHelp*
1609}
1610
1611+ (void) popUpContextMenu: (NSMenu*)menu
1612		withEvent: (NSEvent*)event
1613		  forView: (NSView*)view
1614{
1615  [self popUpContextMenu: menu
1616        withEvent: event
1617        forView: view
1618        withFont: nil];
1619}
1620
1621+ (void) popUpContextMenu: (NSMenu *)menu
1622                withEvent: (NSEvent *)event
1623                  forView: (NSView *)view
1624                 withFont: (NSFont *)font
1625{
1626  [menu _rightMouseDisplay: event];
1627}
1628
1629/*
1630 * NSObject Protocol
1631 */
1632- (BOOL) isEqual: (id)anObject
1633{
1634  if (self == anObject)
1635    return YES;
1636  if ([anObject isKindOfClass: [NSMenu class]])
1637    {
1638      if (![_title isEqualToString: [anObject title]])
1639	return NO;
1640      return [[self itemArray] isEqual: [anObject itemArray]];
1641    }
1642  return NO;
1643}
1644
1645/*
1646 * NSCoding Protocol
1647 */
1648- (void) encodeWithCoder: (NSCoder*)encoder
1649{
1650  if ([encoder allowsKeyedCoding])
1651    {
1652      [encoder encodeObject: _title forKey: @"NSTitle"];
1653      [encoder encodeObject: _items forKey: @"NSMenuItems"];
1654
1655      if ([self _isMain])
1656	{
1657	  [encoder encodeObject: @"_NSMainMenu" forKey: @"NSName"];
1658	}
1659    }
1660  else
1661    {
1662      BOOL autoenable = _menu.autoenable;
1663
1664      [encoder encodeObject: _title];
1665      [encoder encodeObject: _items];
1666      [encoder encodeValueOfObjCType: @encode(BOOL) at: &autoenable];
1667    }
1668}
1669
1670- (id) initWithCoder: (NSCoder*)aDecoder
1671{
1672  NSString	*dTitle;
1673  NSString	*dName;
1674  NSArray	*dItems;
1675  BOOL		dAuto;
1676  NSUInteger i;
1677  NSUInteger count;
1678
1679  if ([aDecoder allowsKeyedCoding])
1680    {
1681      //
1682      // NSNoAutoenable is present when the "Autoenable" option is NOT checked.
1683      // NO = Autoenable menus, YES = Don't auto enable menus.  We, therefore,
1684      // have to invert the values of this flag in order to get the value of
1685      // dAuto.
1686      //
1687      if ([aDecoder containsValueForKey: @"NSNoAutoenable"])
1688        {
1689	  dAuto = ![aDecoder decodeBoolForKey: @"NSNoAutoenable"];
1690	}
1691      else
1692	{
1693	  dAuto = YES;
1694	}
1695      dTitle = [aDecoder decodeObjectForKey: @"NSTitle"];
1696      dItems = [aDecoder decodeObjectForKey: @"NSMenuItems"];
1697      if ([aDecoder containsValueForKey: @"NSName"])
1698        {
1699	  dName = [aDecoder decodeObjectForKey: @"NSName"];
1700	}
1701      else
1702	{
1703	  dName = nil;
1704	}
1705    }
1706  else
1707    {
1708      dTitle = [aDecoder decodeObject];
1709      dItems = [aDecoder decodeObject];
1710      dName = nil;
1711      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &dAuto];
1712    }
1713  self = [self initWithTitle: dTitle];
1714  [self setAutoenablesItems: dAuto];
1715  [self _setName: dName];
1716
1717  [self setMenuChangedMessagesEnabled: NO];
1718  /*
1719   * Make sure that items and submenus are set correctly.
1720   */
1721  count = [dItems count];
1722  for (i = 0; i < count; i++)
1723    {
1724      NSMenuItem *item = [dItems objectAtIndex: i];
1725      [self addItem: item];
1726    }
1727  [self setMenuChangedMessagesEnabled: YES];
1728
1729  return self;
1730}
1731
1732- (void) awakeFromNib
1733{
1734  NSString *name = [self _name];
1735
1736  if (name)
1737    {
1738      if ([name isEqualToString: @"_NSMainMenu"])
1739        {
1740          // NB This is already handled by the nib loading code
1741          //[NSApp setMainMenu: self];
1742        }
1743      else if ([name isEqualToString: @"_NSAppleMenu"])
1744        {
1745          // GNUstep does not handle Apple's application menu specially
1746        }
1747      else if ([name isEqualToString: @"_NSWindowsMenu"])
1748        {
1749          [NSApp setWindowsMenu: self];
1750        }
1751      else if ([name isEqualToString: @"_NSServicesMenu"])
1752        {
1753          [NSApp setServicesMenu: self];
1754        }
1755      else if ([name isEqualToString: @"_NSRecentDocumentsMenu"])
1756        {
1757          [[NSDocumentController sharedDocumentController]
1758                  _setRecentDocumentsMenu: self];
1759        }
1760      else if ([name isEqualToString: @"_NSFontMenu"])
1761        {
1762          [[NSFontManager sharedFontManager] setFontMenu: self];
1763        }
1764    }
1765}
1766
1767/*
1768 * NSCopying Protocol
1769 */
1770- (id) copyWithZone: (NSZone*)zone
1771{
1772  NSMenu *new = [[NSMenu allocWithZone: zone] initWithTitle: _title];
1773  NSUInteger i;
1774  NSUInteger count = [_items count];
1775
1776  [new setAutoenablesItems: _menu.autoenable];
1777  for (i = 0; i < count; i++)
1778    {
1779      // This works because the copy on NSMenuItem sets the menu to nil!!!
1780      NSMenuItem *item = [[_items objectAtIndex: i] copyWithZone: zone];
1781      [new insertItem: item atIndex: i];
1782      RELEASE(item);
1783    }
1784
1785  return new;
1786}
1787
1788@end
1789
1790@implementation NSMenu (GNUstepExtra)
1791
1792- (void) setTornOff: (BOOL)flag
1793{
1794  NSMenu *supermenu;
1795
1796  _menu.is_tornoff = flag;
1797
1798  if (flag)
1799    {
1800      supermenu = [self supermenu];
1801      if (supermenu != nil)
1802        {
1803          [[supermenu menuRepresentation] setHighlightedItemIndex: -1];
1804          supermenu->_attachedMenu = nil;
1805        }
1806      [nc addObserver: self
1807             selector: @selector(windowDidChangeScreen:)
1808                 name: NSWindowDidBecomeKeyNotification
1809               object: nil];
1810      [nc addObserver: self
1811             selector: @selector(windowDidChangeScreen:)
1812                 name: NSWindowDidChangeScreenNotification
1813               object: nil];
1814    }
1815  else
1816    {
1817      [nc removeObserver: self
1818                    name: NSWindowDidBecomeKeyNotification
1819                  object: nil];
1820      [nc removeObserver: self
1821                    name: NSWindowDidChangeScreenNotification
1822                  object: nil];
1823    }
1824  [_view update];
1825}
1826
1827- (void) _showTornOffMenuIfAny: (NSNotification*)notification
1828{
1829  NSInterfaceStyle style = NSInterfaceStyleForKey(@"NSMenuInterfaceStyle", nil);
1830  if (style == NSMacintoshInterfaceStyle
1831    || style == NSWindows95InterfaceStyle)
1832    {
1833      return;
1834    }
1835  if (![self _isMain])
1836    {
1837      NSString		*key;
1838
1839      key = [self _locationKey];
1840      if (key != nil)
1841	{
1842	  NSString		*location;
1843	  NSUserDefaults	*defaults;
1844	  NSDictionary		*menuLocations;
1845
1846	  defaults  = [NSUserDefaults standardUserDefaults];
1847	  menuLocations = [defaults objectForKey: NSMenuLocationsKey];
1848
1849	  if ([menuLocations isKindOfClass: [NSDictionary class]])
1850	    location = [menuLocations objectForKey: key];
1851	  else
1852	    location = nil;
1853	  if (location && [location isKindOfClass: [NSString class]])
1854	    {
1855	      [self setTornOff: YES];
1856	      [self display];
1857	    }
1858	}
1859    }
1860}
1861
1862- (void) applicationDidFinishLaunching:(NSNotification *)notification
1863{
1864  if (NSInterfaceStyleForKey(@"NSMenuInterfaceStyle", nil) ==
1865      NSWindows95InterfaceStyle)
1866    {
1867      [[GSTheme theme] updateAllWindowsWithMenu: [NSApp mainMenu]];
1868    }
1869  [self _showTornOffMenuIfAny: notification];
1870
1871  if ([NSApp mainMenu] == self)
1872    {
1873      [nc addObserver: self
1874             selector: @selector(windowDidChangeScreen:)
1875                 name: NSWindowDidBecomeKeyNotification
1876               object: nil];
1877      [nc addObserver: self
1878             selector: @selector(windowDidChangeScreen:)
1879                 name: NSWindowDidChangeScreenNotification
1880               object: nil];
1881    }
1882}
1883
1884- (void) _showOnActivateApp: (NSNotification*)notification
1885{
1886  if ([self _isMain])
1887  {
1888    [self display];
1889    // we must make sure that any attached submenu is visible too.
1890    [[self attachedMenu] display];
1891  }
1892}
1893
1894- (void) windowDidChangeScreen: (NSNotification*)notification
1895{
1896  NSWindow *window = [notification object];
1897  NSRect   frame;
1898  NSRect   oldScreenFrame;
1899  NSRect   newScreenFrame;
1900  CGFloat  yOffset;
1901
1902  if ([window isKindOfClass: [NSPanel class]]
1903      || window == _aWindow
1904      || [window isKeyWindow] == NO
1905      || [_aWindow screen] == [window screen]
1906      || [_aWindow isVisible] == NO)
1907    {
1908      return;
1909    }
1910
1911  oldScreenFrame = [[_aWindow screen] frame];
1912  newScreenFrame = [[window screen] frame];
1913  frame = [_aWindow frame];
1914
1915  // Keep left offset fixed
1916  frame.origin.x += newScreenFrame.origin.x - oldScreenFrame.origin.x;
1917
1918  // Keep top offset fixed
1919  yOffset = NSMaxY(oldScreenFrame) - NSMaxY(frame);
1920  frame.origin.y = NSMaxY(newScreenFrame) - yOffset - frame.size.height;
1921
1922  // setFrame: changes _screen value.
1923  [self nestedSetFrameOrigin: frame.origin];
1924}
1925
1926- (BOOL) isTransient
1927{
1928  return _menu.transient;
1929}
1930
1931- (BOOL) isPartlyOffScreen
1932{
1933  NSWindow *window;
1934
1935  window = [self window];
1936  if ((nil != window) && (nil != [window screen]))
1937    {
1938      return !NSContainsRect([[window screen] visibleFrame], [window frame]);
1939    }
1940  else
1941    {
1942      NSLog(@"Menu has no window %@ or screen %@", window, [window screen]);
1943      return YES;
1944    }
1945}
1946
1947- (void) _performMenuClose: (id)sender
1948{
1949  if (_attachedMenu)
1950    [_view detachSubmenu];
1951
1952  [_view setHighlightedItemIndex: -1];
1953  [self close];
1954  [self setTornOff: NO];
1955  [self _updateUserDefaults: nil];
1956}
1957
1958- (void) display
1959{
1960  if (_menu.transient)
1961    {
1962      NSDebugLLog (@"NSMenu",
1963                   @"trying to display while already displayed transient");
1964    }
1965
1966  [self update];
1967  if (_menu.needsSizing)
1968    {
1969      [self sizeToFit];
1970    }
1971
1972  if (_superMenu && ![self isTornOff])
1973    {
1974      // query super menu for position
1975      [_aWindow setFrameOrigin: [_superMenu locationForSubmenu: self]];
1976      _superMenu->_attachedMenu = self;
1977    }
1978  else if ([_aWindow frame].origin.y <= 0
1979    && _popUpButtonCell == nil)   // get geometry only if not set
1980    {
1981      [self _setGeometry];
1982    }
1983
1984  NSDebugLLog (@"NSMenu",
1985               @"Display, origin: %@",
1986               NSStringFromPoint ([_aWindow frame].origin));
1987
1988  [_aWindow orderFrontRegardless];
1989}
1990
1991- (void) displayTransient
1992{
1993  NSPoint location;
1994  NSView *contentView;
1995
1996  if (_menu.transient)
1997    {
1998      NSDebugLLog (@"NSMenu", @"displaying transient while it is transient");
1999      return;
2000    }
2001
2002  [self update];
2003  if (_menu.needsSizing)
2004    {
2005      [self sizeToFit];
2006    }
2007
2008  _oldHiglightedIndex = [[self menuRepresentation] highlightedItemIndex];
2009  _menu.transient = YES;
2010
2011  /*
2012   * Cache the old submenu if any and query the supermenu our position.
2013   * Otherwise, raise menu under the mouse.
2014   */
2015  if (_superMenu != nil)
2016    {
2017      _oldAttachedMenu = _superMenu->_attachedMenu;
2018      _superMenu->_attachedMenu = self;
2019      location = [_superMenu locationForSubmenu: self];
2020    }
2021  else
2022    {
2023      NSRect	frame = [_aWindow frame];
2024      NSInterfaceStyle style;
2025
2026      location = [_aWindow mouseLocationOutsideOfEventStream];
2027      location = [_aWindow convertBaseToScreen: location];
2028      location.y -= frame.size.height;
2029
2030      /* When using the standard NextStep/OpenStep interface style, the
2031	 center of the menu's title view is placed below the mouse cursor.
2032	 However, in Macintosh and Windows95 styles, menus have no visible
2033	 title. To prevent the user from accidentally selecting the first
2034	 item, the top left edge is placed below the mouse cursor for them. */
2035      style = NSInterfaceStyleForKey(@"NSMenuInterfaceStyle", nil);
2036      if (style != NSWindows95InterfaceStyle &&
2037	  style != NSMacintoshInterfaceStyle)
2038	{
2039	  location.x -= frame.size.width/2;
2040	  if (location.x < 0)
2041	    location.x = 0;
2042	  location.y += 10;
2043	}
2044    }
2045
2046  [_bWindow setFrameOrigin: location];
2047
2048  [_view removeFromSuperviewWithoutNeedingDisplay];
2049
2050  contentView = [_bWindow contentView];
2051  [contentView addSubview: _view];
2052
2053  [_view update];
2054
2055  [_bWindow orderFront: self];
2056
2057  /* Right mouse buttons which display transient menus don't update
2058   * the cursor. So, the current cursor is displayed over the
2059   * contextual menu (for example an I beam). However, when menu is
2060   * closed the cursor pop, this can set a wrong cursor. We push here
2061   * an arrow cursor, the cursor we want at menus. Being sure that
2062   * this will pop when menu closes.
2063   */
2064  [[NSCursor arrowCursor] push];
2065}
2066
2067- (void) close
2068{
2069  NSMenu *sub = [self attachedMenu];
2070
2071  if (_menu.transient)
2072    {
2073      NSDebugLLog (@"NSMenu", @"We should not close ordinary menu while transient version is still open");
2074    }
2075
2076  /*
2077   * If we have an attached submenu, we must close that too - but then make
2078   * sure we still have a record of it so that it can be re-displayed if we
2079   * are redisplayed.
2080   */
2081  if (sub != nil)
2082    {
2083      [sub close];
2084      _attachedMenu = sub;
2085    }
2086  [_aWindow orderOut: self];
2087
2088  if (_superMenu && ![self isTornOff])
2089    {
2090      _superMenu->_attachedMenu = nil;
2091      [[_superMenu menuRepresentation] setHighlightedItemIndex: -1];
2092    }
2093}
2094
2095- (void) closeTransient
2096{
2097  NSView *contentView;
2098
2099  if (_menu.transient == NO)
2100    {
2101      NSDebugLLog (@"NSMenu",
2102	@"Closing transient: %@ while it is NOT transient now", _title);
2103      return;
2104    }
2105
2106  [_bWindow orderOut: self];
2107  [_view removeFromSuperviewWithoutNeedingDisplay];
2108
2109  contentView = [_aWindow contentView];
2110  [contentView addSubview: _view];
2111
2112  [contentView setNeedsDisplay: YES];
2113
2114  // Restore the old submenu (if any).
2115  if (_superMenu != nil)
2116    {
2117      _superMenu->_attachedMenu = _oldAttachedMenu;
2118      [[_superMenu menuRepresentation] setHighlightedItemIndex:
2119	[_superMenu indexOfItemWithSubmenu: _superMenu->_attachedMenu]];
2120    }
2121
2122  [[self menuRepresentation] setHighlightedItemIndex: _oldHiglightedIndex];
2123
2124  _menu.transient = NO;
2125  [_view update];
2126}
2127
2128- (NSWindow*) window
2129{
2130  if (_menu.transient)
2131    return (NSWindow *)_bWindow;
2132  else
2133    return (NSWindow *)_aWindow;
2134}
2135
2136- (void) setMain: (BOOL)isMain
2137{
2138  if (isMain)
2139    {
2140      NSMenuView	*oldRep;
2141      NSInterfaceStyle	oldStyle;
2142      NSInterfaceStyle	newStyle;
2143
2144      oldRep = [self menuRepresentation];
2145      oldStyle = [oldRep interfaceStyle];
2146      newStyle = NSInterfaceStyleForKey(@"NSMenuInterfaceStyle", nil);
2147
2148      /*
2149       * If necessary, rebuild menu for (different) style
2150       */
2151      if (oldStyle != newStyle)
2152        {
2153          NSMenuView *newRep;
2154
2155          if (oldStyle == NSWindows95InterfaceStyle)
2156            {
2157              /* Remove the menu from all main windows.
2158               */
2159              [[GSTheme theme] updateAllWindowsWithMenu: nil];
2160            }
2161          if (newStyle == NSWindows95InterfaceStyle)
2162            {
2163              [self close];
2164            }
2165          newRep = [[NSMenuView alloc] initWithFrame: NSZeroRect];
2166          if (newStyle == NSMacintoshInterfaceStyle
2167              || newStyle == NSWindows95InterfaceStyle)
2168            {
2169              [newRep setHorizontal: YES];
2170            }
2171          else
2172            {
2173              [newRep setHorizontal: NO];
2174            }
2175          [newRep setInterfaceStyle: newStyle];
2176          [self setMenuRepresentation: newRep];
2177          [self _organizeMenu];
2178          RELEASE(newRep);
2179          if (newStyle == NSWindows95InterfaceStyle)
2180            {
2181              /* Put menu in all main windows for microsoft style.
2182               */
2183              [[GSTheme theme] updateAllWindowsWithMenu: self];
2184            }
2185        }
2186
2187      /* Adjust the menu window to suit the menu view unless the menu
2188       * is being displayed in the application main window.
2189       */
2190      if (newStyle != NSWindows95InterfaceStyle)
2191        {
2192          [[self window] setTitle: [[NSProcessInfo processInfo] processName]];
2193          [[self window] setLevel: NSMainMenuWindowLevel];
2194          [self _setGeometry];
2195          [self sizeToFit];
2196
2197          if ([NSApp isActive])
2198            {
2199              [self display];
2200            }
2201        }
2202    }
2203  else
2204    {
2205      [[self window] setLevel: NSSubmenuWindowLevel];
2206    }
2207}
2208
2209/**
2210   Set the frame origin of the receiver to aPoint. If a submenu of
2211   the receiver is attached. The frame origin of the submenu is set
2212   appropriately.
2213*/
2214- (void) nestedSetFrameOrigin: (NSPoint) aPoint
2215{
2216  NSWindow *theWindow = [self window];
2217
2218  // Move ourself and get our width.
2219  [theWindow setFrameOrigin: aPoint];
2220
2221  // Do the same for attached menus.
2222  if (_attachedMenu)
2223    {
2224      aPoint = [self locationForSubmenu: _attachedMenu];
2225      [_attachedMenu nestedSetFrameOrigin: aPoint];
2226    }
2227}
2228
2229#define SHIFT_DELTA 18.0
2230
2231- (void) shiftOnScreen
2232{
2233  NSWindow *theWindow = [self window];
2234  NSRect    frameRect = [theWindow frame];
2235  NSRect    screenRect = [[theWindow screen] visibleFrame];
2236  NSPoint   vector    = {0.0, 0.0};
2237  BOOL      moveIt    = NO;
2238  NSPoint location = [theWindow mouseLocationOutsideOfEventStream];
2239  NSPoint pointerLoc = [theWindow convertBaseToScreen: location];
2240  NSInterfaceStyle style = NSInterfaceStyleForKey(@"NSMenuInterfaceStyle", nil);
2241
2242  // Don't move the main menu bar in Macintosh interface style, this is
2243  // annoying (in particular, since the effective screen range is reduced
2244  // by the height of the menu bar!)
2245  if (style == NSMacintoshInterfaceStyle && [self _isMain])
2246    return;
2247
2248  // 1 - determine the amount we need to shift in the y direction.
2249  if (pointerLoc.y <= 1 && NSMinY (frameRect) < 0)
2250    {
2251      vector.y = MIN (SHIFT_DELTA, -NSMinY (frameRect));
2252      moveIt = YES;
2253    }
2254  /* Note: pointerLoc.y may be greater than NSMaxY(screenRect) if we have a
2255     horizontal menu bar at the top of the screen (Macintosh interface style) */
2256  // FIXME Don't move the horizontal menu bar downward in this case!
2257  else if (pointerLoc.y >= NSMaxY(screenRect) &&
2258	   NSMaxY (frameRect) > NSMaxY (screenRect))
2259    {
2260      vector.y = -MIN (SHIFT_DELTA, NSMaxY (frameRect) - NSMaxY (screenRect));
2261      moveIt = YES;
2262    }
2263
2264  // 2 - determine the amount we need to shift in the x direction.
2265  if (pointerLoc.x == 0 && NSMinX (frameRect) < 0)
2266    {
2267      vector.x = MIN (SHIFT_DELTA, -NSMinX (frameRect));
2268      moveIt = YES;
2269    }
2270  // Note the -3.  This is done so the menu, after shifting completely
2271  // has some spare room on the right hand side.  This is needed otherwise
2272  // the user can never access submenus of this menu.
2273  else if (pointerLoc.x == NSMaxX(screenRect) - 1 &&
2274	   NSMaxX (frameRect) > NSMaxX (screenRect) - 3)
2275    {
2276      vector.x
2277	= -MIN (SHIFT_DELTA, NSMaxX (frameRect) - NSMaxX (screenRect) + 3);
2278      moveIt = YES;
2279    }
2280
2281  if (moveIt)
2282    {
2283      NSPoint  masterLocation;
2284      NSPoint  destinationPoint;
2285
2286      if (style == NSMacintoshInterfaceStyle || _menu.horizontal)
2287        {
2288          masterLocation = frameRect.origin;
2289          destinationPoint.x = masterLocation.x + vector.x;
2290          destinationPoint.y = masterLocation.y + vector.y;
2291          [self nestedSetFrameOrigin: destinationPoint];
2292        }
2293      else
2294        {
2295          NSMenu  *candidateMenu;
2296          NSMenu  *masterMenu;
2297
2298          // Look for the "master" menu, i.e. the one to move from.
2299          for (candidateMenu = masterMenu = self;
2300               (candidateMenu = masterMenu->_superMenu)
2301                   && !candidateMenu->_menu.horizontal
2302                   && (!masterMenu->_menu.is_tornoff
2303                       || masterMenu->_menu.transient);
2304               masterMenu = candidateMenu);
2305
2306          masterLocation = [[masterMenu window] frame].origin;
2307          destinationPoint.x = masterLocation.x + vector.x;
2308          destinationPoint.y = masterLocation.y + vector.y;
2309          [masterMenu nestedSetFrameOrigin: destinationPoint];
2310        }
2311    }
2312}
2313
2314- (BOOL)_ownedByPopUp
2315{
2316  return _popUpButtonCell != nil;
2317}
2318
2319- (NSPopUpButtonCell *)_owningPopUp
2320{
2321  return _popUpButtonCell;
2322}
2323
2324- (void)_setOwnedByPopUp: (NSPopUpButtonCell*)popUp
2325{
2326  if (_popUpButtonCell != popUp)
2327    {
2328      _popUpButtonCell = popUp;
2329      if (popUp != nil)
2330	{
2331	  [_aWindow setLevel: NSPopUpMenuWindowLevel];
2332	  [_bWindow setLevel: NSPopUpMenuWindowLevel];
2333	}
2334    }
2335  [self update];
2336}
2337
2338- (NSString*) description
2339{
2340  return [NSString stringWithFormat: @"NSMenu: %@ (%@)",
2341            _title, _menu.transient ? @"Transient": @"Normal"];
2342}
2343
2344@end
2345
2346