1 #include "FGCocoaMenuBar.hxx"
3 #include <Cocoa/Cocoa.h>
5 #include <boost/foreach.hpp>
7 #include <simgear/props/props.hxx>
8 #include <simgear/props/props_io.hxx>
9 #include <simgear/debug/logstream.hxx>
10 #include <simgear/structure/SGBinding.hxx>
11 #include <simgear/misc/strutils.hxx>
13 #include <Main/fg_props.hxx>
14 #include <GUI/CocoaHelpers_private.h>
21 using namespace simgear;
23 typedef std::map<NSMenuItem*, SGBindingList> MenuItemBindings;
25 @class CocoaMenuDelegate;
27 class FGCocoaMenuBar::CocoaMenuBarPrivate
30 CocoaMenuBarPrivate();
31 ~CocoaMenuBarPrivate();
33 void menuFromProps(NSMenu* menu, SGPropertyNode* menuNode);
35 void fireBindingsForItem(NSMenuItem* item);
38 CocoaMenuDelegate* delegate;
40 MenuItemBindings itemBindings;
41 std::vector<SGPropertyChangeListener*> listeners;
44 // prior to the 10.6 SDK, NSMenuDelegate was an informal protocol
45 #if __MAC_OS_X_VERSION_MIN_REQUIRED < 1060
46 @protocol NSMenuDelegate <NSObject>
50 @interface CocoaMenuDelegate : NSObject <NSMenuDelegate> {
52 FGCocoaMenuBar::CocoaMenuBarPrivate* peer;
55 @property (nonatomic, assign) FGCocoaMenuBar::CocoaMenuBarPrivate* peer;
58 @implementation CocoaMenuDelegate
62 - (void) itemAction:(id) sender
64 peer->fireBindingsForItem((NSMenuItem*) sender);
69 static void setFunctionKeyShortcut(const std::string& shortcut, NSMenuItem* item)
71 unichar shortcutChar = NSF1FunctionKey;
72 if (shortcut == "F1") {
73 shortcutChar = NSF1FunctionKey;
74 } else if (shortcut == "F2") {
75 shortcutChar = NSF2FunctionKey;
76 } else if (shortcut == "F3") {
77 shortcutChar = NSF3FunctionKey;
78 } else if (shortcut == "F10") {
79 shortcutChar = NSF10FunctionKey;
80 } else if (shortcut == "F11") {
81 shortcutChar = NSF11FunctionKey;
82 } else if (shortcut == "F12") {
83 shortcutChar = NSF12FunctionKey;
85 SG_LOG(SG_GENERAL, SG_WARN, "CocoaMenu:setFunctionKeyShortcut: unsupported:" << shortcut);
90 [item setKeyEquivalentModifierMask:NSFunctionKeyMask];
91 [item setKeyEquivalent:[NSString stringWithCharacters:ch length:1]];
97 static void setItemShortcutFromString(NSMenuItem* item, const string& s)
101 bool hasCtrl = strutils::starts_with(s, "Ctrl-");
102 bool hasShift = strutils::starts_with(s, "Shift-");
103 bool hasAlt = strutils::starts_with(s, "Alt-");
105 int offset = 0; // character offset from start of string
106 if (hasShift) offset += 6;
107 if (hasCtrl) offset += 5;
108 if (hasAlt) offset += 4;
110 shortcut = s.substr(offset);
111 if (shortcut == "Esc")
114 if ((shortcut.length() >= 2) && (shortcut[0] == 'F') && isdigit(shortcut[1])) {
115 setFunctionKeyShortcut(shortcut, item);
119 simgear::strutils::lowercase(shortcut);
120 [item setKeyEquivalent:[NSString stringWithCString:shortcut.c_str() encoding:NSUTF8StringEncoding]];
121 NSUInteger modifiers = 0;
122 if (hasCtrl) modifiers |= NSControlKeyMask;
123 if (hasShift) modifiers |= NSShiftKeyMask;
124 if (hasAlt) modifiers |= NSAlternateKeyMask;
126 [item setKeyEquivalentModifierMask:modifiers];
131 class CocoaEnabledListener : public SGPropertyChangeListener
134 CocoaEnabledListener(NSMenuItem* i) :
140 ~CocoaEnabledListener()
146 virtual void valueChanged(SGPropertyNode *node)
148 CocoaAutoreleasePool pool;
149 BOOL b = node->getBoolValue();
156 } // of anonymous namespace
158 FGCocoaMenuBar::CocoaMenuBarPrivate::CocoaMenuBarPrivate()
160 delegate = [[CocoaMenuDelegate alloc] init];
161 delegate.peer = this;
164 FGCocoaMenuBar::CocoaMenuBarPrivate::~CocoaMenuBarPrivate()
166 CocoaAutoreleasePool pool;
170 static bool labelIsSeparator(NSString* s)
172 return [s hasPrefix:@"---"];
175 void FGCocoaMenuBar::CocoaMenuBarPrivate::menuFromProps(NSMenu* menu, SGPropertyNode* menuNode)
178 BOOST_FOREACH(SGPropertyNode_ptr n, menuNode->getChildren("item")) {
179 if (!n->hasValue("enabled")) {
180 n->setBoolValue("enabled", true);
183 string l = getLocalizedLabel(n);
184 NSString* label = stdStringToCocoa(strutils::simplify(l));
185 string shortcut = n->getStringValue("key");
188 if (index >= [menu numberOfItems]) {
189 if (labelIsSeparator(label)) {
190 item = [NSMenuItem separatorItem];
193 item = [menu addItemWithTitle:label action:nil keyEquivalent:@""];
194 if (!shortcut.empty()) {
195 setItemShortcutFromString(item, shortcut);
198 SGPropertyChangeListener* enableListener = new CocoaEnabledListener(item);
199 listeners.push_back(enableListener);
200 n->getNode("enabled")->addChangeListener(enableListener);
201 [item setTarget:delegate];
202 [item setAction:@selector(itemAction:)];
205 item = [menu itemAtIndex:index];
206 [item setTitle:label];
209 BOOL enabled = n->getBoolValue("enabled");
210 [item setEnabled:enabled];
212 SGBindingList bl = readBindingList(n->getChildren("binding"), globals->get_props());
214 itemBindings[item] = bl;
216 } // of item iteration
219 void FGCocoaMenuBar::CocoaMenuBarPrivate::fireBindingsForItem(NSMenuItem *item)
221 MenuItemBindings::iterator it = itemBindings.find(item);
222 if (it == itemBindings.end()) {
226 fireBindingList(it->second);
229 FGCocoaMenuBar::FGCocoaMenuBar() :
230 p(new CocoaMenuBarPrivate)
235 FGCocoaMenuBar::~FGCocoaMenuBar()
237 CocoaAutoreleasePool ap;
238 NSMenu* mainBar = [[NSApplication sharedApplication] mainMenu];
240 int num = [mainBar numberOfItems];
241 for (int index=1; index < num; ++index) {
242 NSMenuItem* topLevelItem = [mainBar itemAtIndex:index];
243 [topLevelItem.submenu removeAllItems];
246 std::vector<SGPropertyChangeListener*>::iterator it;
247 for (it = p->listeners.begin(); it != p->listeners.end(); ++it) {
252 void FGCocoaMenuBar::init()
254 CocoaAutoreleasePool pool;
256 NSMenu* mainBar = [[NSApplication sharedApplication] mainMenu];
257 SGPropertyNode_ptr props = fgGetNode("/sim/menubar/default",true);
260 NSMenuItem* previousMenu = [mainBar itemAtIndex:0];
261 if (![[previousMenu title] isEqualToString:@"FlightGear"]) {
262 [previousMenu setTitle:@"FlightGear"];
265 BOOST_FOREACH(SGPropertyNode_ptr n, props->getChildren("menu")) {
266 NSString* label = stdStringToCocoa(getLocalizedLabel(n));
267 NSMenuItem* item = [mainBar itemWithTitle:label];
271 NSInteger insertIndex = [mainBar indexOfItem:previousMenu] + 1;
272 item = [mainBar insertItemWithTitle:label action:nil keyEquivalent:@"" atIndex:insertIndex];
273 item.tag = index + 400;
275 menu = [[NSMenu alloc] init];
277 [menu setAutoenablesItems:NO];
278 [mainBar setSubmenu:menu forItem:item];
284 // synchronise menu with properties
285 p->menuFromProps(menu, n);
289 // track menu enable/disable state
290 if (!n->hasValue("enabled")) {
291 n->setBoolValue("enabled", true);
294 SGPropertyChangeListener* l = new CocoaEnabledListener(item);
295 p->listeners.push_back(l);
296 n->getNode("enabled")->addChangeListener(l);
300 bool FGCocoaMenuBar::isVisible() const
305 void FGCocoaMenuBar::show()
310 void FGCocoaMenuBar::hide()