1 #include "FGCocoaMenuBar.hxx"
3 #include <AppKit/NSMenu.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;
29 class CocoaEnabledListener : public SGPropertyChangeListener
32 CocoaEnabledListener(SGPropertyNode_ptr prop, NSMenuItem* i) :
33 property(prop->getNode("enabled")),
37 property->addChangeListener(this);
41 ~CocoaEnabledListener()
44 property->removeChangeListener(this);
49 virtual void valueChanged(SGPropertyNode *node)
51 CocoaAutoreleasePool pool;
52 BOOL b = node->getBoolValue();
57 SGPropertyNode_ptr property;
60 } // of anonymous namespace
62 class FGCocoaMenuBar::CocoaMenuBarPrivate
65 CocoaMenuBarPrivate();
66 ~CocoaMenuBarPrivate();
68 void menuFromProps(NSMenu* menu, SGPropertyNode* menuNode);
70 void fireBindingsForItem(NSMenuItem* item);
73 CocoaMenuDelegate* delegate;
75 MenuItemBindings itemBindings;
76 std::vector<CocoaEnabledListener*> listeners;
79 // prior to the 10.6 SDK, NSMenuDelegate was an informal protocol
80 #if __MAC_OS_X_VERSION_MIN_REQUIRED < 1060
81 @protocol NSMenuDelegate <NSObject>
85 @interface CocoaMenuDelegate : NSObject <NSMenuDelegate> {
87 FGCocoaMenuBar::CocoaMenuBarPrivate* peer;
90 @property (nonatomic, assign) FGCocoaMenuBar::CocoaMenuBarPrivate* peer;
93 @implementation CocoaMenuDelegate
97 - (void) itemAction:(id) sender
99 peer->fireBindingsForItem((NSMenuItem*) sender);
104 static void setFunctionKeyShortcut(const std::string& shortcut, NSMenuItem* item)
106 unichar shortcutChar = NSF1FunctionKey;
107 if (shortcut == "F1") {
108 shortcutChar = NSF1FunctionKey;
109 } else if (shortcut == "F2") {
110 shortcutChar = NSF2FunctionKey;
111 } else if (shortcut == "F3") {
112 shortcutChar = NSF3FunctionKey;
113 } else if (shortcut == "F10") {
114 shortcutChar = NSF10FunctionKey;
115 } else if (shortcut == "F11") {
116 shortcutChar = NSF11FunctionKey;
117 } else if (shortcut == "F12") {
118 shortcutChar = NSF12FunctionKey;
120 SG_LOG(SG_GENERAL, SG_WARN, "CocoaMenu:setFunctionKeyShortcut: unsupported:" << shortcut);
124 ch[0] = shortcutChar;
125 [item setKeyEquivalentModifierMask:NSFunctionKeyMask];
126 [item setKeyEquivalent:[NSString stringWithCharacters:ch length:1]];
132 static void setItemShortcutFromString(NSMenuItem* item, const string& s)
134 std::string shortcut;
136 bool hasCtrl = strutils::starts_with(s, "Ctrl-");
137 bool hasShift = strutils::starts_with(s, "Shift-");
138 bool hasAlt = strutils::starts_with(s, "Alt-");
140 int offset = 0; // character offset from start of string
141 if (hasShift) offset += 6;
142 if (hasCtrl) offset += 5;
143 if (hasAlt) offset += 4;
145 shortcut = s.substr(offset);
146 if (shortcut == "Esc")
149 if ((shortcut.length() >= 2) && (shortcut[0] == 'F') && isdigit(shortcut[1])) {
150 setFunctionKeyShortcut(shortcut, item);
154 simgear::strutils::lowercase(shortcut);
155 [item setKeyEquivalent:[NSString stringWithCString:shortcut.c_str() encoding:NSUTF8StringEncoding]];
156 NSUInteger modifiers = 0;
157 if (hasCtrl) modifiers |= NSControlKeyMask;
158 if (hasShift) modifiers |= NSShiftKeyMask;
159 if (hasAlt) modifiers |= NSAlternateKeyMask;
161 [item setKeyEquivalentModifierMask:modifiers];
164 FGCocoaMenuBar::CocoaMenuBarPrivate::CocoaMenuBarPrivate()
166 delegate = [[CocoaMenuDelegate alloc] init];
167 delegate.peer = this;
170 FGCocoaMenuBar::CocoaMenuBarPrivate::~CocoaMenuBarPrivate()
172 CocoaAutoreleasePool pool;
176 static bool labelIsSeparator(NSString* s)
178 return [s hasPrefix:@"---"];
181 void FGCocoaMenuBar::CocoaMenuBarPrivate::menuFromProps(NSMenu* menu, SGPropertyNode* menuNode)
184 BOOST_FOREACH(SGPropertyNode_ptr n, menuNode->getChildren("item")) {
185 if (!n->hasValue("enabled")) {
186 n->setBoolValue("enabled", true);
189 string l = getLocalizedLabel(n);
190 NSString* label = stdStringToCocoa(strutils::simplify(l));
191 string shortcut = n->getStringValue("key");
194 if (index >= [menu numberOfItems]) {
195 if (labelIsSeparator(label)) {
196 item = [NSMenuItem separatorItem];
199 item = [menu addItemWithTitle:label action:nil keyEquivalent:@""];
200 if (!shortcut.empty()) {
201 setItemShortcutFromString(item, shortcut);
204 CocoaEnabledListener* cl = new CocoaEnabledListener(n, item);
205 listeners.push_back(cl);
207 [item setTarget:delegate];
208 [item setAction:@selector(itemAction:)];
211 item = [menu itemAtIndex:index];
212 [item setTitle:label];
215 BOOL enabled = n->getBoolValue("enabled");
216 [item setEnabled:enabled];
218 SGBindingList bl = readBindingList(n->getChildren("binding"), globals->get_props());
220 itemBindings[item] = bl;
222 } // of item iteration
225 void FGCocoaMenuBar::CocoaMenuBarPrivate::fireBindingsForItem(NSMenuItem *item)
227 MenuItemBindings::iterator it = itemBindings.find(item);
228 if (it == itemBindings.end()) {
232 fireBindingList(it->second);
235 FGCocoaMenuBar::FGCocoaMenuBar() :
236 p(new CocoaMenuBarPrivate)
241 FGCocoaMenuBar::~FGCocoaMenuBar()
243 CocoaAutoreleasePool ap;
244 NSMenu* mainBar = [[NSApplication sharedApplication] mainMenu];
246 int num = [mainBar numberOfItems];
247 for (int index=1; index < num; ++index) {
248 NSMenuItem* topLevelItem = [mainBar itemAtIndex:index];
249 [topLevelItem.submenu removeAllItems];
252 std::vector<CocoaEnabledListener*>::iterator it;
253 for (it = p->listeners.begin(); it != p->listeners.end(); ++it) {
257 // owing to the bizarre destructor behaviour of SGBinding, we need
258 // to explicitly clear these bindings. (PUIMenuBar takes a different
259 // approach, and copies each binding into /sim/bindings)
260 MenuItemBindings::iterator j;
261 for (j = p->itemBindings.begin(); j != p->itemBindings.end(); ++j) {
262 clearBindingList(j->second);
266 void FGCocoaMenuBar::init()
268 CocoaAutoreleasePool pool;
270 NSMenu* mainBar = [[NSApplication sharedApplication] mainMenu];
271 SGPropertyNode_ptr props = fgGetNode("/sim/menubar/default",true);
274 NSMenuItem* previousMenu = [mainBar itemAtIndex:0];
275 if (![[previousMenu title] isEqualToString:@"FlightGear"]) {
276 [previousMenu setTitle:@"FlightGear"];
279 BOOST_FOREACH(SGPropertyNode_ptr n, props->getChildren("menu")) {
280 NSString* label = stdStringToCocoa(getLocalizedLabel(n));
281 NSMenuItem* item = [mainBar itemWithTitle:label];
285 NSInteger insertIndex = [mainBar indexOfItem:previousMenu] + 1;
286 item = [mainBar insertItemWithTitle:label action:nil keyEquivalent:@"" atIndex:insertIndex];
287 item.tag = index + 400;
289 menu = [[NSMenu alloc] init];
291 [menu setAutoenablesItems:NO];
292 [mainBar setSubmenu:menu forItem:item];
298 // synchronise menu with properties
299 p->menuFromProps(menu, n);
303 // track menu enable/disable state
304 if (!n->hasValue("enabled")) {
305 n->setBoolValue("enabled", true);
308 CocoaEnabledListener* l = new CocoaEnabledListener( n, item);
309 p->listeners.push_back(l);
313 bool FGCocoaMenuBar::isVisible() const
318 void FGCocoaMenuBar::show()
323 void FGCocoaMenuBar::hide()