Improve behavior of menubar switching when windows are activated.
[wxWidgets.git] / src / cocoa / mbarman.mm
1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        cocoa/mbarman.cpp
3 // Purpose:     wxMenuBarManager implementation
4 // Author:      David Elliott
5 // Modified by:
6 // Created:     2003/09/04
7 // RCS-ID:      $Id$
8 // Copyright:   (c) 2003 David Elliott
9 // Licence:     wxWidgets licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 #include "wx/wxprec.h"
13 #if wxUSE_MENUS
14 #ifndef WX_PRECOMP
15     #include "wx/log.h"
16     #include "wx/app.h"
17     #include "wx/menu.h"
18     #include "wx/toplevel.h"
19 #endif // WX_PRECOMP
20
21 #include "wx/cocoa/mbarman.h"
22 #include "wx/cocoa/autorelease.h"
23 #include "wx/cocoa/objc/objc_uniquifying.h"
24
25 #import <Foundation/NSString.h>
26 #import <Foundation/NSNotification.h>
27 #import <AppKit/NSMenu.h>
28 #import <AppKit/NSApplication.h>
29 #import <AppKit/NSWindow.h>
30
31 #define wxUSE_FSCRIPT 0
32 #if wxUSE_FSCRIPT
33     #import <FScript/FScriptMenuItem.h>
34 #endif
35
36 // Declare setAppleMenu: in an NSApplication category since Tiger and later
37 // releases support it but don't declare it as it's considered deprecated.
38 @interface NSApplication(wxDeprecatedMethodsWeWantToUse)
39 - (void)setAppleMenu:(NSMenu *)menu;
40 @end
41
42 // ============================================================================
43 // wxMenuBarManagerObserver
44 // ============================================================================
45 @interface wxMenuBarManagerObserver : NSObject
46 {
47     wxMenuBarManager *m_mbarman;
48 }
49
50 - (id)init;
51 - (id)initWithWxMenuBarManager: (wxMenuBarManager *)mbarman;
52 - (void)windowDidBecomeKey: (NSNotification *)notification;
53 #if 0
54 - (void)windowDidResignKey: (NSNotification *)notification;
55 - (void)windowDidBecomeMain: (NSNotification *)notification;
56 - (void)windowDidResignMain: (NSNotification *)notification;
57 - (void)windowWillClose: (NSNotification *)notification;
58 #endif // 0
59 @end // interface wxMenuBarManagerObserver : NSObject
60 WX_DECLARE_GET_OBJC_CLASS(wxMenuBarManagerObserver,NSObject)
61
62 @implementation wxMenuBarManagerObserver : NSObject
63 - (id)init
64 {
65     wxFAIL_MSG(wxT("[wxMenuBarManagerObserver -init] should never be called!"));
66     m_mbarman = NULL;
67     return self;
68 }
69
70 - (id)initWithWxMenuBarManager: (wxMenuBarManager *)mbarman
71 {
72     wxASSERT(mbarman);
73     m_mbarman = mbarman;
74     return [super init];
75 }
76
77 - (void)windowDidBecomeKey: (NSNotification *)notification
78 {
79     wxASSERT(m_mbarman);
80     m_mbarman->WindowDidBecomeKey(notification);
81 }
82
83 #if 0
84 - (void)windowDidResignKey: (NSNotification *)notification
85 {
86     wxASSERT(m_mbarman);
87     m_mbarman->WindowDidResignKey(notification);
88 }
89
90 - (void)windowDidBecomeMain: (NSNotification *)notification
91 {
92     wxASSERT(m_mbarman);
93     m_mbarman->WindowDidBecomeMain(notification);
94 }
95
96 - (void)windowDidResignMain: (NSNotification *)notification
97 {
98     wxASSERT(m_mbarman);
99     m_mbarman->WindowDidResignMain(notification);
100 }
101
102 - (void)windowWillClose: (NSNotification *)notification
103 {
104     wxASSERT(m_mbarman);
105     m_mbarman->WindowWillClose(notification);
106 }
107 #endif // 0
108
109 @end // implementation wxMenuBarManagerObserver : NSObject
110 WX_IMPLEMENT_GET_OBJC_CLASS(wxMenuBarManagerObserver,NSObject)
111
112 // ============================================================================
113 // wxMenuBarManager
114 // ============================================================================
115 wxMenuBarManager *wxMenuBarManager::sm_mbarmanInstance = NULL;
116
117 static void AddFScriptItem(NSMenu *menu)
118 #if wxUSE_FSCRIPT
119 {
120     NSMenuItem *item = [[FScriptMenuItem alloc] init];
121     [menu addItem: item];
122     [item release];
123 }
124 #else
125 {}
126 #endif
127
128 wxMenuBarManager::wxMenuBarManager()
129 {
130     m_observer = [[WX_GET_OBJC_CLASS(wxMenuBarManagerObserver) alloc]
131             initWithWxMenuBarManager:this];
132     [[NSNotificationCenter defaultCenter] addObserver:m_observer
133             selector:@selector(windowDidBecomeKey:)
134             name:NSWindowDidBecomeKeyNotification object:nil];
135
136     // HACK: Reuse the same selector and eventual C++ method and make it
137     // check for whether the notification is to become key or main.
138     [[NSNotificationCenter defaultCenter] addObserver:m_observer
139             selector:@selector(windowDidBecomeKey:)
140             name:NSWindowDidBecomeMainNotification object:nil];
141 #if 0
142     [[NSNotificationCenter defaultCenter] addObserver:m_observer
143             selector:@selector(windowDidResignKey:)
144             name:NSWindowDidResignKeyNotification object:nil];
145     [[NSNotificationCenter defaultCenter] addObserver:m_observer
146             selector:@selector(windowDidBecomeMain:)
147             name:NSWindowDidBecomeMainNotification object:nil];
148     [[NSNotificationCenter defaultCenter] addObserver:m_observer
149             selector:@selector(windowDidResignMain:)
150             name:NSWindowDidResignMainNotification object:nil];
151     [[NSNotificationCenter defaultCenter] addObserver:m_observer
152             selector:@selector(windowWillClose:)
153             name:NSWindowWillCloseNotification object:nil];
154 #endif // 0
155     m_menuApp = nil;
156     m_menuServices = nil;
157     m_menuWindows = nil;
158     m_menuMain = nil;
159     m_mainMenuBarInstalled = true;
160     m_mainMenuBar = NULL;
161     m_currentNSWindow = nil;
162
163     NSApplication *theNSApplication = wxTheApp->GetNSApplication();
164     // Create the services menu.
165     m_menuServices = [[NSMenu alloc] initWithTitle: @"Services"];
166     [theNSApplication setServicesMenu:m_menuServices];
167
168     NSMenuItem *menuitem;
169     // Create the application (Apple) menu.
170     m_menuApp = [[NSMenu alloc] initWithTitle: @"Apple Menu"];
171
172 /**/[m_menuApp addItemWithTitle:@"Preferences..." action:nil keyEquivalent:@""];
173 /**/[m_menuApp addItem: [NSMenuItem separatorItem]];
174 /**/AddFScriptItem(m_menuApp);
175 /**/menuitem = [[NSMenuItem alloc] initWithTitle: @"Services" action:nil keyEquivalent:@""];
176     [menuitem setSubmenu:m_menuServices];
177     [m_menuApp addItem: menuitem];
178     [menuitem release];
179 /**/[m_menuApp addItem: [NSMenuItem separatorItem]];
180 /**/menuitem = [[NSMenuItem alloc] initWithTitle:@"Hide" action:@selector(hide:) keyEquivalent:@""];
181     [menuitem setTarget: theNSApplication];
182     [m_menuApp addItem: menuitem];
183     [menuitem release];
184 /**/menuitem = [[NSMenuItem alloc] initWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@""];
185     [menuitem setTarget: theNSApplication];
186     [m_menuApp addItem: menuitem];
187     [menuitem release];
188 /**/menuitem = [[NSMenuItem alloc] initWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
189     [menuitem setTarget: theNSApplication];
190     [m_menuApp addItem: menuitem];
191     [menuitem release];
192 /**/[m_menuApp addItem: [NSMenuItem separatorItem]];
193 /**/menuitem = [[NSMenuItem alloc] initWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@"q"];
194     [menuitem setTarget: theNSApplication];
195     [m_menuApp addItem: menuitem];
196     [menuitem release];
197
198     [theNSApplication setAppleMenu:m_menuApp];
199
200     // Create the Windows menu
201     m_menuWindows = [[NSMenu alloc] initWithTitle: @"Window"];
202
203 /**/[m_menuWindows addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@""];
204 /**/[m_menuWindows addItem: [NSMenuItem separatorItem]];
205 /**/[m_menuWindows addItemWithTitle:@"Bring All to Front" action:@selector(arrangeInFront:) keyEquivalent:@""];
206
207     [theNSApplication setWindowsMenu:m_menuWindows];
208
209     // Create the main menubar
210     m_menuMain = [[NSMenu alloc] initWithTitle: @"wxApp Menu"];
211 /**/NSMenuItem *dummyItem = [[NSMenuItem alloc] initWithTitle:@"App menu"
212         /* Note: title gets clobbered by app name anyway */
213         action:nil keyEquivalent:@""];
214     [dummyItem setSubmenu:m_menuApp];
215     [m_menuMain addItem:dummyItem];
216     [dummyItem release];
217 /**/dummyItem = [[NSMenuItem alloc] initWithTitle:@"Window"
218         action:nil keyEquivalent:@""];
219     [dummyItem setSubmenu:m_menuWindows];
220     [m_menuMain addItem:dummyItem];
221     [dummyItem release];
222
223     [theNSApplication setMainMenu: m_menuMain];
224
225 }
226
227 wxMenuBarManager::~wxMenuBarManager()
228 {
229     [m_observer release];
230 }
231
232 void wxMenuBarManager::CreateInstance()
233 {
234     sm_mbarmanInstance = new wxMenuBarManager;
235 }
236
237 void wxMenuBarManager::DestroyInstance()
238 {
239     delete sm_mbarmanInstance;
240     sm_mbarmanInstance = NULL;
241 }
242
243 void wxMenuBarManager::SetMenuBar(wxMenuBar* menubar)
244 {
245     m_mainMenuBarInstalled = false;
246     if(menubar)
247     {
248         [[[wxTheApp->GetNSApplication() mainMenu] itemAtIndex:0] setSubmenu:nil];
249         [[menubar->GetNSMenu() itemAtIndex:0] setSubmenu:m_menuApp];
250         [wxTheApp->GetNSApplication() setMainMenu:menubar->GetNSMenu()];
251     }
252     else
253         InstallMainMenu();
254 }
255
256 void wxMenuBarManager::SetMainMenuBar(wxMenuBar* menubar)
257 {
258     m_mainMenuBar = menubar;
259     if(m_mainMenuBarInstalled)
260         InstallMainMenu();
261 }
262
263 void wxMenuBarManager::InstallMainMenu()
264 {
265     if(m_mainMenuBar)
266         SetMenuBar(m_mainMenuBar);
267     else
268     {
269         m_mainMenuBarInstalled = true;
270         [[[wxTheApp->GetNSApplication() mainMenu] itemAtIndex:0] setSubmenu:nil];
271         [[m_menuMain itemAtIndex:0] setSubmenu:m_menuApp];
272         [wxTheApp->GetNSApplication() setMainMenu:m_menuMain];
273     }
274 }
275
276 void wxMenuBarManager::WindowDidBecomeKey(NSNotification *notification)
277 {
278     /* NOTE: m_currentNSWindow might be destroyed but we only ever use it
279        to look it up in the hash table.  Do not send messages to it. */
280
281     /*  Update m_currentNSWindow only if we really should.  For instance,
282         if a non-wx window is becoming key but a wx window remains main
283         then don't change out the menubar.  However, if a non-wx window
284         (whether the same window or not) is main, then switch to the
285         generic menubar so the wx window that last installed a menubar
286         doesn't get menu events it doesn't expect.
287
288         If a wx window is becoming main then check to see if the key
289         window is a wx window and if so do nothing because that
290         is what would have been done before.
291
292         If a non-wx window is becoming main and 
293      */
294     NSString *notificationName = [notification name];
295     if(NULL == notificationName)
296         return;
297     else if([NSWindowDidBecomeKeyNotification isEqualTo:notificationName])
298     {   // This is the only one that was handled in 2.8 as shipped
299         // Generally the key window can change without the main window changing.
300         // The user can do this simply by clicking on something in a palette window
301         // that needs to become key.
302         NSWindow *newKeyWindow = [notification object];
303         wxCocoaNSWindow *theWxKeyWindow = wxCocoaNSWindow::GetFromCocoa(newKeyWindow);
304         if(theWxKeyWindow != NULL)
305         {   // If the new key window is a wx window, handle it as before
306             // even if it has not actually changed.
307             m_currentNSWindow = newKeyWindow;
308         }
309         else
310         {   // If the new key window is not wx then check the main window.
311             NSWindow *mainWindow = [[NSApplication sharedApplication] mainWindow];
312             if(m_currentNSWindow == mainWindow)
313                 // Don't reset if the menubar doesn't need to change.
314                 return;
315             else
316                 // This is strange because theoretically we should have picked this up
317                 // already in the main window notification but it's possible that
318                 // we simply haven't gotten it yet and will about as soon as we return.
319                 // We already know that the key window isn't wx so fall back to this
320                 // one and let the code go ahead and set the wx menubar if it is
321                 // a wx window and set the generic one if it isn't.
322                 m_currentNSWindow = mainWindow;
323         }
324     }
325     else if([NSWindowDidBecomeMainNotification isEqualTo:notificationName])
326     {   // Handling this is new
327         // Generally the main window cannot change without the key window changing
328         // because if the user clicks on a window that can become main then the
329         // window will also become key.
330         // However, it's possible that when it becomes main it automatically makes
331         // some palette the key window.
332         NSWindow *newMainWindow = [notification object];
333         // If we already know about the window, bail.
334         if(newMainWindow == m_currentNSWindow)
335             return;
336         else
337         {
338             NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
339             if(keyWindow == m_currentNSWindow)
340                 // if we already know about the key window, bail
341                 return;
342             else
343             {   // As above, sort of strange.  Neither one is current.  Prefer key over main.
344                 wxCocoaNSWindow *theWxMainWindow = wxCocoaNSWindow::GetFromCocoa(keyWindow);
345                 if(theWxMainWindow != NULL)
346                     m_currentNSWindow = keyWindow;
347                 else
348                     m_currentNSWindow = newMainWindow;
349             }
350         }
351     }
352     m_currentNSWindow = [notification object];
353     wxCocoaNSWindow *win = wxCocoaNSWindow::GetFromCocoa(m_currentNSWindow);
354     if(win)
355         InstallMenuBarForWindow(win);
356     else
357         SetMenuBar(NULL);
358 }
359
360 #if 0
361 void wxMenuBarManager::WindowDidResignKey(NSNotification *notification)
362 {
363 }
364
365 void wxMenuBarManager::WindowDidBecomeMain(NSNotification *notification)
366 {
367 }
368
369 void wxMenuBarManager::WindowDidResignMain(NSNotification *notification)
370 {
371 }
372
373 void wxMenuBarManager::WindowWillClose(NSNotification *notification)
374 {
375 }
376 #endif // 0
377
378 void wxMenuBarManager::InstallMenuBarForWindow(wxCocoaNSWindow *win)
379 {
380     wxASSERT(win);
381     wxMenuBar *menubar = win->GetAppMenuBar(win);
382     wxLogTrace(wxTRACE_COCOA,wxT("Found menubar=%p for window=%p."),menubar,win);
383     SetMenuBar(menubar);
384 }
385
386 void wxMenuBarManager::UpdateMenuBar()
387 {
388     if(m_currentNSWindow)
389     {
390         wxCocoaNSWindow *win = wxCocoaNSWindow::GetFromCocoa(m_currentNSWindow);
391         if(win)
392             InstallMenuBarForWindow(win);
393     }
394 }
395
396 #endif // wxUSE_MENUS