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>
20 using namespace simgear;
22 typedef std::map<NSMenuItem*, SGBindingList> MenuItemBindings;
24 @class CocoaMenuDelegate;
26 class FGCocoaMenuBar::CocoaMenuBarPrivate
29 CocoaMenuBarPrivate();
30 ~CocoaMenuBarPrivate();
32 void menuFromProps(NSMenu* menu, SGPropertyNode* menuNode);
34 void fireBindingsForItem(NSMenuItem* item);
37 CocoaMenuDelegate* delegate;
39 MenuItemBindings itemBindings;
42 // prior to the 10.6 SDK, NSMenuDelegate was an informal protocol
43 #if __MAC_OS_X_VERSION_MIN_REQUIRED < 1060
44 @protocol NSMenuDelegate <NSObject>
48 @interface CocoaMenuDelegate : NSObject <NSMenuDelegate> {
50 FGCocoaMenuBar::CocoaMenuBarPrivate* peer;
53 @property (nonatomic, assign) FGCocoaMenuBar::CocoaMenuBarPrivate* peer;
56 @implementation CocoaMenuDelegate
60 - (void) itemAction:(id) sender
62 peer->fireBindingsForItem((NSMenuItem*) sender);
67 static NSString* stdStringToCocoa(const string& s)
69 return [NSString stringWithUTF8String:s.c_str()];
72 static void setFunctionKeyShortcut(NSMenuItem* item, unichar shortcut)
76 [item setKeyEquivalentModifierMask:NSFunctionKeyMask];
77 [item setKeyEquivalent:[NSString stringWithCharacters:ch length:1]];
81 static void setItemShortcutFromString(NSMenuItem* item, const string& s)
83 const char* shortcut = "";
85 bool hasCtrl = strutils::starts_with(s, "Ctrl-");
86 bool hasShift = strutils::starts_with(s, "Shift-");
87 bool hasAlt = strutils::starts_with(s, "Alt-");
89 int offset = 0; // character offset from start of string
90 if (hasShift) offset += 6;
91 if (hasCtrl) offset += 5;
92 if (hasAlt) offset += 4;
94 shortcut = s.c_str() + offset;
95 if (!strcmp(shortcut, "Esc"))
98 if (!strcmp(shortcut, "F11")) {
99 setFunctionKeyShortcut(item, NSF11FunctionKey);
103 if (!strcmp(shortcut, "F12")) {
104 setFunctionKeyShortcut(item, NSF12FunctionKey);
108 [item setKeyEquivalent:[NSString stringWithCString:shortcut encoding:NSUTF8StringEncoding]];
109 NSUInteger modifiers = 0;
110 if (hasCtrl) modifiers |= NSControlKeyMask;
111 if (hasShift) modifiers |= NSShiftKeyMask;
112 if (hasAlt) modifiers |= NSAlternateKeyMask;
114 [item setKeyEquivalentModifierMask:modifiers];
118 class CocoaAutoreleasePool
121 CocoaAutoreleasePool()
123 pool = [[NSAutoreleasePool alloc] init];
126 ~CocoaAutoreleasePool()
132 NSAutoreleasePool* pool;
135 class CocoaEnabledListener : public SGPropertyChangeListener
138 CocoaEnabledListener(NSMenuItem* i) :
143 virtual void valueChanged(SGPropertyNode *node)
145 CocoaAutoreleasePool pool;
146 BOOL b = node->getBoolValue();
153 } // of anonymous namespace
155 FGCocoaMenuBar::CocoaMenuBarPrivate::CocoaMenuBarPrivate()
157 delegate = [[CocoaMenuDelegate alloc] init];
158 delegate.peer = this;
161 FGCocoaMenuBar::CocoaMenuBarPrivate::~CocoaMenuBarPrivate()
163 CocoaAutoreleasePool pool;
167 static bool labelIsSeparator(NSString* s)
169 return [s hasPrefix:@"---"];
172 void FGCocoaMenuBar::CocoaMenuBarPrivate::menuFromProps(NSMenu* menu, SGPropertyNode* menuNode)
175 BOOST_FOREACH(SGPropertyNode_ptr n, menuNode->getChildren("item")) {
176 if (!n->hasValue("enabled")) {
177 n->setBoolValue("enabled", true);
180 string l = getLocalizedLabel(n);
181 NSString* label = stdStringToCocoa(strutils::simplify(l));
182 string shortcut = n->getStringValue("key");
185 if (index >= [menu numberOfItems]) {
186 if (labelIsSeparator(label)) {
187 item = [NSMenuItem separatorItem];
190 item = [menu addItemWithTitle:label action:nil keyEquivalent:@""];
191 if (!shortcut.empty()) {
192 setItemShortcutFromString(item, shortcut);
195 n->getNode("enabled")->addChangeListener(new CocoaEnabledListener(item));
196 [item setTarget:delegate];
197 [item setAction:@selector(itemAction:)];
200 item = [menu itemAtIndex:index];
201 [item setTitle:label];
204 BOOL enabled = n->getBoolValue("enabled");
205 [item setEnabled:enabled];
208 BOOST_FOREACH(SGPropertyNode_ptr binding, n->getChildren("binding")) {
209 // have to clone the bindings, since SGBinding takes ownership of the
210 // passed in node. Seems like something is wrong here, but following the
211 // PUI code for the moment.
212 SGPropertyNode* cloned(new SGPropertyNode);
213 copyProperties(binding, cloned);
214 bl.push_back(new SGBinding(cloned, globals->get_props()));
217 itemBindings[item] = bl;
219 } // of item iteration
222 void FGCocoaMenuBar::CocoaMenuBarPrivate::fireBindingsForItem(NSMenuItem *item)
224 MenuItemBindings::iterator it = itemBindings.find(item);
225 if (it == itemBindings.end()) {
229 BOOST_FOREACH(SGSharedPtr<SGBinding> b, it->second) {
234 FGCocoaMenuBar::FGCocoaMenuBar() :
235 p(new CocoaMenuBarPrivate)
240 FGCocoaMenuBar::~FGCocoaMenuBar()
245 void FGCocoaMenuBar::init()
247 CocoaAutoreleasePool pool;
249 NSMenu* mainBar = [[NSApplication sharedApplication] mainMenu];
250 SGPropertyNode_ptr props = fgGetNode("/sim/menubar/default",true);
253 NSMenuItem* previousMenu = [mainBar itemAtIndex:0];
254 if (![[previousMenu title] isEqualToString:@"FlightGear"]) {
255 [previousMenu setTitle:@"FlightGear"];
258 BOOST_FOREACH(SGPropertyNode_ptr n, props->getChildren("menu")) {
259 NSString* label = stdStringToCocoa(getLocalizedLabel(n));
260 NSMenuItem* item = [mainBar itemWithTitle:label];
264 NSInteger insertIndex = [mainBar indexOfItem:previousMenu] + 1;
265 item = [mainBar insertItemWithTitle:label action:nil keyEquivalent:@"" atIndex:insertIndex];
266 item.tag = index + 400;
268 menu = [[NSMenu alloc] init];
270 [menu setAutoenablesItems:NO];
271 [mainBar setSubmenu:menu forItem:item];
277 // synchronise menu with properties
278 p->menuFromProps(menu, n);
282 // track menu enable/disable state
283 if (!n->hasValue("enabled")) {
284 n->setBoolValue("enabled", true);
287 n->getNode("enabled")->addChangeListener(new CocoaEnabledListener(item));
291 bool FGCocoaMenuBar::isVisible() const
296 void FGCocoaMenuBar::show()
301 void FGCocoaMenuBar::hide()
306 void cocoaOpenUrl(const std::string& url)
308 CocoaAutoreleasePool pool;
309 NSURL* nsu = [NSURL URLWithString:stdStringToCocoa(url)];
310 [[NSWorkspace sharedWorkspace] openURL:nsu];