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