From ebfdebeb43ca11e385fc5248fcdd6746899b1c9f Mon Sep 17 00:00:00 2001 From: James Turner Date: Sun, 20 Nov 2011 13:23:52 +0000 Subject: [PATCH] Cocoa menu-bar implementation. --- CMakeLists.txt | 3 +- src/GUI/CMakeLists.txt | 5 + src/GUI/FGCocoaMenuBar.hxx | 65 +++++++++++ src/GUI/FGCocoaMenuBar.mm | 231 +++++++++++++++++++++++++++++++++++++ src/GUI/new_gui.cxx | 21 +++- 5 files changed, 321 insertions(+), 4 deletions(-) create mode 100644 src/GUI/FGCocoaMenuBar.hxx create mode 100644 src/GUI/FGCocoaMenuBar.mm diff --git a/CMakeLists.txt b/CMakeLists.txt index 1fde17c9d..e1b568b0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,7 +65,8 @@ IF(APPLE) set(EVENT_INPUT_DEFAULT 1) find_library(CORESERVICES_LIBRARY CoreServices) - list(APPEND PLATFORM_LIBS ${CORESERVICES_LIBRARY}) + find_library(COCOA_LIBRARY Cocoa) + list(APPEND PLATFORM_LIBS ${COCOA_LIBRARY} ${CORESERVICES_LIBRARY}) elseif(CMAKE_SYSTEM_NAME MATCHES "Linux") # disabled while DBus / HAL / udev issues are decided diff --git a/src/GUI/CMakeLists.txt b/src/GUI/CMakeLists.txt index 030416ef0..b44a90f09 100644 --- a/src/GUI/CMakeLists.txt +++ b/src/GUI/CMakeLists.txt @@ -37,4 +37,9 @@ set(HEADERS FGColor.hxx ) +if (APPLE) + list(APPEND HEADERS FGCocoaMenuBar.hxx) + list(APPEND SOURCES FGCocoaMenuBar.mm) +endif() + flightgear_component(GUI "${SOURCES}" "${HEADERS}") diff --git a/src/GUI/FGCocoaMenuBar.hxx b/src/GUI/FGCocoaMenuBar.hxx new file mode 100644 index 000000000..694561aae --- /dev/null +++ b/src/GUI/FGCocoaMenuBar.hxx @@ -0,0 +1,65 @@ +// menubar.hxx - XML-configured menu bar. + +#ifndef FG_COCOA_MENUBAR_HXX +#define FG_COCOA_MENUBAR_HXX 1 + +#include + +#include + +/** + * XML-configured Cocoa menu bar. + * + * This class creates a menu bar from a tree of XML properties. These + * properties are not part of the main FlightGear property tree, but + * are read from a separate file ($FG_ROOT/gui/menubar.xml). + * + * WARNING: because PUI provides no easy way to attach user data to a + * menu item, all menu item strings must be unique; otherwise, this + * class will always use the first binding with any given name. + */ +class FGCocoaMenuBar : public FGMenuBar +{ +public: + + /** + * Constructor. + */ + FGCocoaMenuBar (); + + + /** + * Destructor. + */ + virtual ~FGCocoaMenuBar (); + + + /** + * Initialize the menu bar from $FG_ROOT/gui/menubar.xml + */ + virtual void init (); + + /** + * Make the menu bar visible. + */ + virtual void show (); + + + /** + * Make the menu bar invisible. + */ + virtual void hide (); + + + /** + * Test whether the menu bar is visible. + */ + virtual bool isVisible () const; + + class CocoaMenuBarPrivate; +private: + std::auto_ptr p; + +}; + +#endif // __MENUBAR_HXX diff --git a/src/GUI/FGCocoaMenuBar.mm b/src/GUI/FGCocoaMenuBar.mm new file mode 100644 index 000000000..7b44c7820 --- /dev/null +++ b/src/GUI/FGCocoaMenuBar.mm @@ -0,0 +1,231 @@ +#include "FGCocoaMenuBar.hxx" + +#include + +#include + +#include +#include +#include +#include + +#include
+ +#include + +using std::string; +using std::map; +using std::cout; + +typedef std::map MenuItemBindings; + +@class CocoaMenuDelegate; + +class FGCocoaMenuBar::CocoaMenuBarPrivate +{ +public: + CocoaMenuBarPrivate(); + ~CocoaMenuBarPrivate(); + + bool labelIsSeparator(const std::string& s) const; + void menuFromProps(NSMenu* menu, SGPropertyNode* menuNode); + + void fireBindingsForItem(NSMenuItem* item); + +public: + CocoaMenuDelegate* delegate; + + MenuItemBindings itemBindings; +}; + + +@interface CocoaMenuDelegate : NSObject { +@private + FGCocoaMenuBar::CocoaMenuBarPrivate* peer; +} + +@property (nonatomic, assign) FGCocoaMenuBar::CocoaMenuBarPrivate* peer; +@end + +@implementation CocoaMenuDelegate + +@synthesize peer; + +- (void) itemAction:(id) sender +{ + peer->fireBindingsForItem((NSMenuItem*) sender); +} + +@end + +static NSString* stdStringToCocoa(const string& s) +{ + return [NSString stringWithUTF8String:s.c_str()]; +} + +class EnabledListener : public SGPropertyChangeListener +{ +public: + EnabledListener(NSMenuItem* i) : + item(i) + {} + + + virtual void valueChanged(SGPropertyNode *node) + { + BOOL b = node->getBoolValue(); + [item setEnabled:b]; + } + +private: + NSMenuItem* item; +}; + + + +FGCocoaMenuBar::CocoaMenuBarPrivate::CocoaMenuBarPrivate() +{ + delegate = [[CocoaMenuDelegate alloc] init]; + delegate.peer = this; +} + +FGCocoaMenuBar::CocoaMenuBarPrivate::~CocoaMenuBarPrivate() +{ + [delegate release]; +} + +bool FGCocoaMenuBar::CocoaMenuBarPrivate::labelIsSeparator(const std::string& s) const +{ + for (unsigned int i=0; igetChildren("item")) { + if (!n->hasValue("enabled")) { + n->setBoolValue("enabled", true); + } + + string l = n->getStringValue("label"); + string::size_type pos = l.find("("); + if (pos != string::npos) { + l = l.substr(0, pos); + } + + NSString* label = stdStringToCocoa(l); + NSString* shortcut = @""; + NSMenuItem* item; + if (index >= [menu numberOfItems]) { + if (labelIsSeparator(l)) { + item = [NSMenuItem separatorItem]; + [menu addItem:item]; + } else { + item = [menu addItemWithTitle:label action:nil keyEquivalent:shortcut]; + n->getNode("enabled")->addChangeListener(new EnabledListener(item)); + [item setTarget:delegate]; + [item setAction:@selector(itemAction:)]; + } + } else { + item = [menu itemAtIndex:index]; + [item setTitle:label]; + } + + BOOL enabled = n->getBoolValue("enabled"); + [item setEnabled:enabled]; + + SGBindingList bl; + BOOST_FOREACH(SGPropertyNode_ptr binding, n->getChildren("binding")) { + // have to clone the bindings, since SGBinding takes ownership of the + // passed in node. Seems like something is wrong here, but following the + // PUI code for the moment. + SGPropertyNode* cloned(new SGPropertyNode); + copyProperties(binding, cloned); + bl.push_back(new SGBinding(cloned, globals->get_props())); + } + + itemBindings[item] = bl; + ++index; + } // of item iteration +} + +void FGCocoaMenuBar::CocoaMenuBarPrivate::fireBindingsForItem(NSMenuItem *item) +{ + MenuItemBindings::iterator it = itemBindings.find(item); + if (it == itemBindings.end()) { + return; + } + + BOOST_FOREACH(SGSharedPtr b, it->second) { + b->fire(); + } +} + +FGCocoaMenuBar::FGCocoaMenuBar() : + p(new CocoaMenuBarPrivate) +{ + +} + +FGCocoaMenuBar::~FGCocoaMenuBar() +{ + +} + +void FGCocoaMenuBar::init() +{ + NSMenu* mainBar = [[NSApplication sharedApplication] mainMenu]; + SGPropertyNode_ptr props = fgGetNode("/sim/menubar/default",true); + + int index = 0; + NSMenuItem* previousMenu = [mainBar itemAtIndex:0]; + if (![[previousMenu title] isEqualToString:@"FlightGear"]) { + [previousMenu setTitle:@"FlightGear"]; + } + + BOOST_FOREACH(SGPropertyNode_ptr n, props->getChildren("menu")) { + NSString* label = stdStringToCocoa(n->getStringValue("label")); + NSMenuItem* item = [mainBar itemWithTitle:label]; + NSMenu* menu; + + if (!item) { + NSInteger insertIndex = [mainBar indexOfItem:previousMenu] + 1; + item = [mainBar insertItemWithTitle:label action:nil keyEquivalent:@"" atIndex:insertIndex]; + item.tag = index + 400; + + menu = [[NSMenu alloc] init]; + menu.title = label; + [menu setAutoenablesItems:NO]; + [mainBar setSubmenu:menu forItem:item]; + [menu autorelease]; + } else { + menu = item.submenu; + } + + // synchronise menu with properties + p->menuFromProps(menu, n); + ++index; + previousMenu = item; + } +} + +bool FGCocoaMenuBar::isVisible() const +{ + return true; +} + +void FGCocoaMenuBar::show() +{ + // no-op +} + +void FGCocoaMenuBar::hide() +{ + // no-op +} diff --git a/src/GUI/new_gui.cxx b/src/GUI/new_gui.cxx index f9d7941c2..ef50d7731 100644 --- a/src/GUI/new_gui.cxx +++ b/src/GUI/new_gui.cxx @@ -27,6 +27,11 @@ #endif #include "FGPUIMenuBar.hxx" + +#if defined(SG_MAC) +#include "FGCocoaMenuBar.hxx" +#endif + #include "FGPUIDialog.hxx" #include "FGFontCache.hxx" #include "FGColor.hxx" @@ -40,10 +45,14 @@ using std::string; -NewGUI::NewGUI () - : _menubar(new FGPUIMenuBar), - _active_dialog(0) +NewGUI::NewGUI () : + _active_dialog(0) { +#if defined(SG_MAC) + _menubar.reset(new FGCocoaMenuBar); +#else + _menubar.reset(new FGPUIMenuBar); +#endif } NewGUI::~NewGUI () @@ -91,7 +100,13 @@ NewGUI::reset (bool reload) setStyle(); unbind(); +#if defined(SG_MAC) + if (reload) { + _menubar.reset(new FGCocoaMenuBar); + } +#else _menubar.reset(new FGPUIMenuBar); +#endif if (reload) { _dialog_props.clear(); -- 2.39.5