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