From a6dba586ee944b5f5ff125c2154cc47ca19ce46e Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Sat, 21 Sep 2013 14:01:51 +0200 Subject: [PATCH] Implement Application Menu for OS X (close #11) --- src/SFML/Window/CMakeLists.txt | 6 +- src/SFML/Window/OSX/AutoreleasePoolWrapper.mm | 8 +- src/SFML/Window/OSX/SFApplication.h | 9 +- src/SFML/Window/OSX/SFApplication.m | 197 ++++++++++++++++++ src/SFML/Window/OSX/SFApplicationDelegate.h | 42 ++++ src/SFML/Window/OSX/SFApplicationDelegate.m | 52 +++++ src/SFML/Window/OSX/SFWindow.h | 24 ++- src/SFML/Window/OSX/SFWindow.m | 14 ++ src/SFML/Window/OSX/WindowImplCocoa.mm | 9 + 9 files changed, 351 insertions(+), 10 deletions(-) create mode 100644 src/SFML/Window/OSX/SFApplicationDelegate.h create mode 100644 src/SFML/Window/OSX/SFApplicationDelegate.m diff --git a/src/SFML/Window/CMakeLists.txt b/src/SFML/Window/CMakeLists.txt index b1d9aca60..5aef7e88a 100644 --- a/src/SFML/Window/CMakeLists.txt +++ b/src/SFML/Window/CMakeLists.txt @@ -80,13 +80,15 @@ else() # MACOSX ${SRCROOT}/OSX/JoystickImpl.hpp ${SRCROOT}/OSX/SFApplication.h ${SRCROOT}/OSX/SFApplication.m + ${SRCROOT}/OSX/SFApplicationDelegate.h + ${SRCROOT}/OSX/SFApplicationDelegate.m ${SRCROOT}/OSX/SFContext.hpp ${SRCROOT}/OSX/SFContext.mm ${SRCROOT}/OSX/SFKeyboardModifiersHelper.h ${SRCROOT}/OSX/SFKeyboardModifiersHelper.mm ${SRCROOT}/OSX/SFOpenGLView.h - ${SRCROOT}/OSX/SFOpenGLView.mm - ${SRCROOT}/OSX/SFSilentResponder.h + ${SRCROOT}/OSX/SFOpenGLView.mm + ${SRCROOT}/OSX/SFSilentResponder.h ${SRCROOT}/OSX/SFSilentResponder.m ${SRCROOT}/OSX/SFWindow.h ${SRCROOT}/OSX/SFWindow.m diff --git a/src/SFML/Window/OSX/AutoreleasePoolWrapper.mm b/src/SFML/Window/OSX/AutoreleasePoolWrapper.mm index b03b33c95..41dbf95d3 100644 --- a/src/SFML/Window/OSX/AutoreleasePoolWrapper.mm +++ b/src/SFML/Window/OSX/AutoreleasePoolWrapper.mm @@ -88,7 +88,7 @@ public : /// \brief Drain the pool /// //////////////////////////////////////////////////////////// - void Drain(); + void drain(); private: @@ -155,7 +155,7 @@ void PoolWrapper::release() // Drain pool if required. if (m_count == 0) { - Drain(); + drain(); } #ifdef SFML_DEBUG @@ -165,7 +165,7 @@ void PoolWrapper::release() #endif } -void PoolWrapper::Drain() +void PoolWrapper::drain() { [m_pool drain]; m_pool = 0; @@ -227,7 +227,7 @@ void releasePool(void) void drainPool() { if (localPool != NULL) { - localPool->Drain(); + localPool->drain(); } #ifdef SFML_DEBUG else { diff --git a/src/SFML/Window/OSX/SFApplication.h b/src/SFML/Window/OSX/SFApplication.h index c30bd1ada..351b08fea 100644 --- a/src/SFML/Window/OSX/SFApplication.h +++ b/src/SFML/Window/OSX/SFApplication.h @@ -30,7 +30,7 @@ #import //////////////////////////////////////////////////////////// -/// \brief Event processing +/// \brief Event processing & Menu bar initialisation /// //////////////////////////////////////////////////////////// @interface SFApplication : NSApplication @@ -43,6 +43,13 @@ +(void)processEvent; +//////////////////////////////////////////////////////////// +/// \brief Set up the menu bar and its items +/// +//////////////////////////////////////////////////////////// ++(void)setUpMenuBar; + + //////////////////////////////////////////////////////////// /// \brief Dispatch events /// diff --git a/src/SFML/Window/OSX/SFApplication.m b/src/SFML/Window/OSX/SFApplication.m index d7b350a9c..6af69dded 100644 --- a/src/SFML/Window/OSX/SFApplication.m +++ b/src/SFML/Window/OSX/SFApplication.m @@ -48,6 +48,203 @@ } } + +//////////////////////////////////////////////////////// ++(void)setUpMenuBar +{ + [SFApplication sharedApplication]; // Make sure NSApp exists + + // Set the main menu bar + NSMenu* mainMenu = [NSApp mainMenu]; + if (mainMenu != nil) return; + mainMenu = [[NSMenu alloc] initWithTitle:@""]; + [NSApp setMainMenu:mainMenu]; + + // Application Menu (aka Apple Menu) + NSMenuItem* appleItem = [mainMenu addItemWithTitle:@"" action:nil keyEquivalent:@""]; + NSMenu* appleMenu = [[SFApplication createAppleMenu] autorelease]; + [appleItem setSubmenu:appleMenu]; + + // File Menu + NSMenuItem* fileItem = [mainMenu addItemWithTitle:@"" action:nil keyEquivalent:@""]; + NSMenu* fileMenu = [[SFApplication createFileMenu] autorelease]; + [fileItem setSubmenu:fileMenu]; + + // Window menu + NSMenuItem* windowItem = [mainMenu addItemWithTitle:@"" action:nil keyEquivalent:@""]; + NSMenu* windowMenu = [[SFApplication createWindowMenu] autorelease]; + [windowItem setSubmenu:windowMenu]; + [NSApp setWindowsMenu:windowMenu]; +} + + +//////////////////////////////////////////////////////// ++(NSMenu *)createAppleMenu +{ + // Apple menu is as follow: + // + // AppName > + // About AppName + // -------------------- + // Preferences... [greyed] + // -------------------- + // Services > + // / default empty menu / + // -------------------- + // Hide AppName ⌘H + // Hide Others ⌥⌘H + // Show All + // -------------------- + // Quit AppName ⌘Q + + NSString* appName = [SFApplication applicationName]; + + // APPLE MENU + NSMenu* appleMenu = [[NSMenu alloc] initWithTitle:@""]; + + // ABOUT + [appleMenu addItemWithTitle:[@"About " stringByAppendingString:appName] + action:@selector(orderFrontStandardAboutPanel:) + keyEquivalent:@""]; + + // SEPARATOR + [appleMenu addItem:[NSMenuItem separatorItem]]; + + // PREFERENCES + [appleMenu addItemWithTitle:@"Preferences…" + action:nil + keyEquivalent:@""]; + + // SEPARATOR + [appleMenu addItem:[NSMenuItem separatorItem]]; + + // SERVICES + NSMenu* serviceMenu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; + NSMenuItem* serviceItem = [appleMenu addItemWithTitle:@"Services" + action:nil + keyEquivalent:@""]; + [serviceItem setSubmenu:serviceMenu]; + [NSApp setServicesMenu:serviceMenu]; + + // SEPARATOR + [appleMenu addItem:[NSMenuItem separatorItem]]; + + // HIDE + [appleMenu addItemWithTitle:[@"Hide " stringByAppendingString:appName] + action:@selector(hide:) + keyEquivalent:@"h"]; + + // HIDE OTHER + NSMenuItem* hideOtherItem = [appleMenu addItemWithTitle:@"Hide Others" + action:@selector(hideOtherApplications:) + keyEquivalent:@"h"]; + [hideOtherItem setKeyEquivalentModifierMask:(NSAlternateKeyMask | NSCommandKeyMask)]; + + // SHOW ALL + [appleMenu addItemWithTitle:@"Show All" + action:@selector(unhideAllApplications:) + keyEquivalent:@""]; + + // SEPARATOR + [appleMenu addItem:[NSMenuItem separatorItem]]; + + // QUIT + [appleMenu addItemWithTitle:[@"Quit " stringByAppendingString:appName] + action:@selector(terminate:) + keyEquivalent:@"q"]; + + return appleMenu; +} + + +//////////////////////////////////////////////////////// ++(NSMenu *)createFileMenu +{ + // The File menu is as follow: + // + // File > + // Close ⌘W + + // FILE MENU + NSMenu* fileMenu = [[NSMenu alloc] initWithTitle:@"File"]; + + // CLOSE WINDOW + NSMenuItem* closeItem = [[NSMenuItem alloc] initWithTitle:@"Close Window" + action:@selector(performClose:) + keyEquivalent:@"w"]; + [fileMenu addItem:closeItem]; + [closeItem release]; + + return fileMenu; +} + + +//////////////////////////////////////////////////////// ++(NSMenu *)createWindowMenu +{ + // The Window menu is as follow: + // + // Window > + // Minimize ⌘M + // Zoom + // -------------------- + // Bring All to Front + + // WINDOW MENU + NSMenu* windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; + + // MINIMIZE + NSMenuItem* minimizeItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" + action:@selector(performMiniaturize:) + keyEquivalent:@"m"]; + [windowMenu addItem:minimizeItem]; + [minimizeItem release]; + + // ZOOM + [windowMenu addItemWithTitle:@"Zoom" + action:@selector(performZoom:) + keyEquivalent:@""]; + + // SEPARATOR + [windowMenu addItem:[NSMenuItem separatorItem]]; + + // BRING ALL TO FRONT + [windowMenu addItemWithTitle:@"Bring All to Front" + action:@selector(bringAllToFront:) + keyEquivalent:@""]; + + return windowMenu; +} + + +//////////////////////////////////////////////////////// ++(NSString *)applicationName +{ + // First, try localized name + NSString* appName = [[[NSBundle mainBundle] localizedInfoDictionary] objectForKey:@"CFBundleDisplayName"]; + + // Then, try non-localized name + if (appName == nil || [appName length] == 0) { + appName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; + } + + // Finally, fallback to the process info + if (appName == nil || [appName length] == 0) { + appName = [[NSProcessInfo processInfo] processName]; + } + + return appName; +} + + +//////////////////////////////////////////////////////// +-(void)bringAllToFront:(id)sender +{ + [[NSApp windows] makeObjectsPerformSelector:@selector(orderFrontRegardless)]; +} + + +//////////////////////////////////////////////////////// -(void)sendEvent:(NSEvent *)anEvent { // Fullscreen windows have a strange behaviour with key up. To make diff --git a/src/SFML/Window/OSX/SFApplicationDelegate.h b/src/SFML/Window/OSX/SFApplicationDelegate.h new file mode 100644 index 000000000..4eac47799 --- /dev/null +++ b/src/SFML/Window/OSX/SFApplicationDelegate.h @@ -0,0 +1,42 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2013 Marco Antognini (antognini.marco@gmail.com), +// Laurent Gomila (laurent.gom@gmail.com), +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#import +#import + + +//////////////////////////////////////////////////////////// +/// \brief Process some application specific events +/// +//////////////////////////////////////////////////////////// +@interface SFApplicationDelegate : NSObject + +-(NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender; +-(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication; + +@end diff --git a/src/SFML/Window/OSX/SFApplicationDelegate.m b/src/SFML/Window/OSX/SFApplicationDelegate.m new file mode 100644 index 000000000..c3bec0093 --- /dev/null +++ b/src/SFML/Window/OSX/SFApplicationDelegate.m @@ -0,0 +1,52 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2013 Marco Antognini (antognini.marco@gmail.com), +// Laurent Gomila (laurent.gom@gmail.com), +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#import + +//////////////////////////////////////////////////////////// +@implementation SFApplicationDelegate + + +//////////////////////////////////////////////////////////// +-(NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender +{ + // TODO generate close event for each SFML window + [NSApp makeWindowsPerform:@selector(sfClose) inOrder:NO]; + return NSTerminateCancel; +} + + +//////////////////////////////////////////////////////////// +-(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication +{ + return YES; +} + + +@end + diff --git a/src/SFML/Window/OSX/SFWindow.h b/src/SFML/Window/OSX/SFWindow.h index c274001c3..3a2a98ade 100644 --- a/src/SFML/Window/OSX/SFWindow.h +++ b/src/SFML/Window/OSX/SFWindow.h @@ -32,9 +32,7 @@ /// \brief Here we redefine some methods to allow grabing fullscreen events. /// //////////////////////////////////////////////////////////// -@interface SFWindow : NSWindow { - -} +@interface SFWindow : NSWindow //////////////////////////////////////////////////////////// /// These two methods must return YES to grab fullscreen events. @@ -45,4 +43,24 @@ -(BOOL)acceptsFirstResponder; -(BOOL)canBecomeKeyWindow; +//////////////////////////////////////////////////////////// +/// Override default implementation of keyDown: to prevent +/// system alert +/// +//////////////////////////////////////////////////////////// +-(void)keyDown:(NSEvent *)theEvent; + +@end + + +@interface NSWindow (SFML) + +//////////////////////////////////////////////////////////// +/// Proxy for performClose: for the app delegate +/// +/// Always return nil +/// +//////////////////////////////////////////////////////////// +-(id)sfClose; + @end diff --git a/src/SFML/Window/OSX/SFWindow.m b/src/SFML/Window/OSX/SFWindow.m index f316aa083..75c39fa11 100644 --- a/src/SFML/Window/OSX/SFWindow.m +++ b/src/SFML/Window/OSX/SFWindow.m @@ -44,6 +44,7 @@ return YES; } + //////////////////////////////////////////////////////// -(void)keyDown:(NSEvent *)theEvent { @@ -56,4 +57,17 @@ // alert to be thrown everytime the user presses a key. } + +@end + + +@implementation NSWindow (SFML) + +//////////////////////////////////////////////////////////// +-(id)sfClose +{ + [self performClose:nil]; + return nil; +} + @end diff --git a/src/SFML/Window/OSX/WindowImplCocoa.mm b/src/SFML/Window/OSX/WindowImplCocoa.mm index 010a70dcf..018cf5fc0 100644 --- a/src/SFML/Window/OSX/WindowImplCocoa.mm +++ b/src/SFML/Window/OSX/WindowImplCocoa.mm @@ -35,6 +35,7 @@ #import #import #import +#import namespace sf { @@ -137,6 +138,14 @@ void WindowImplCocoa::setUpProcess(void) SetFrontProcess(&psn); } + // Register an application delegate if there is none + if (![[SFApplication sharedApplication] delegate]) { + [NSApp setDelegate:[[SFApplicationDelegate alloc] init]]; + } + + // Create menus for the application (before finishing launching!) + [SFApplication setUpMenuBar]; + // Tell the application to stop bouncing in the Dock. [[SFApplication sharedApplication] finishLaunching]; // NOTE : This last call won't harm anything even if SFML window was