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;
43 @interface CocoaMenuDelegate : NSObject <NSMenuDelegate> {
45 FGCocoaMenuBar::CocoaMenuBarPrivate* peer;
48 @property (nonatomic, assign) FGCocoaMenuBar::CocoaMenuBarPrivate* peer;
51 @implementation CocoaMenuDelegate
55 - (void) itemAction:(id) sender
57 peer->fireBindingsForItem((NSMenuItem*) sender);
62 static NSString* stdStringToCocoa(const string& s)
64 return [NSString stringWithUTF8String:s.c_str()];
67 static void setFunctionKeyShortcut(NSMenuItem* item, unichar shortcut)
71 [item setKeyEquivalentModifierMask:NSFunctionKeyMask];
72 [item setKeyEquivalent:[NSString stringWithCharacters:ch length:1]];
76 static void setItemShortcutFromString(NSMenuItem* item, const string& s)
78 const char* shortcut = "";
80 bool hasCtrl = strutils::starts_with(s, "Ctrl-");
81 bool hasShift = strutils::starts_with(s, "Shift-");
82 bool hasAlt = strutils::starts_with(s, "Alt-");
84 int offset = 0; // character offset from start of string
85 if (hasShift) offset += 6;
86 if (hasCtrl) offset += 5;
87 if (hasAlt) offset += 4;
89 shortcut = s.c_str() + offset;
90 if (!strcmp(shortcut, "Esc"))
93 if (!strcmp(shortcut, "F11")) {
94 setFunctionKeyShortcut(item, NSF11FunctionKey);
98 if (!strcmp(shortcut, "F12")) {
99 setFunctionKeyShortcut(item, NSF12FunctionKey);
103 [item setKeyEquivalent:[NSString stringWithCString:shortcut encoding:NSUTF8StringEncoding]];
104 NSUInteger modifiers = 0;
105 if (hasCtrl) modifiers |= NSControlKeyMask;
106 if (hasShift) modifiers |= NSShiftKeyMask;
107 if (hasAlt) modifiers |= NSAlternateKeyMask;
109 [item setKeyEquivalentModifierMask:modifiers];
113 class CocoaAutoreleasePool
116 CocoaAutoreleasePool()
118 pool = [[NSAutoreleasePool alloc] init];
121 ~CocoaAutoreleasePool()
127 NSAutoreleasePool* pool;
130 class CocoaEnabledListener : public SGPropertyChangeListener
133 CocoaEnabledListener(NSMenuItem* i) :
138 virtual void valueChanged(SGPropertyNode *node)
140 CocoaAutoreleasePool pool;
141 BOOL b = node->getBoolValue();
148 } // of anonymous namespace
150 FGCocoaMenuBar::CocoaMenuBarPrivate::CocoaMenuBarPrivate()
152 delegate = [[CocoaMenuDelegate alloc] init];
153 delegate.peer = this;
156 FGCocoaMenuBar::CocoaMenuBarPrivate::~CocoaMenuBarPrivate()
158 CocoaAutoreleasePool pool;
162 static bool labelIsSeparator(NSString* s)
164 return [s hasPrefix:@"---"];
167 void FGCocoaMenuBar::CocoaMenuBarPrivate::menuFromProps(NSMenu* menu, SGPropertyNode* menuNode)
170 BOOST_FOREACH(SGPropertyNode_ptr n, menuNode->getChildren("item")) {
171 if (!n->hasValue("enabled")) {
172 n->setBoolValue("enabled", true);
175 string l = getLocalizedLabel(n);
176 NSString* label = stdStringToCocoa(strutils::simplify(l));
177 string shortcut = n->getStringValue("key");
180 if (index >= [menu numberOfItems]) {
181 if (labelIsSeparator(label)) {
182 item = [NSMenuItem separatorItem];
185 item = [menu addItemWithTitle:label action:nil keyEquivalent:@""];
186 if (!shortcut.empty()) {
187 setItemShortcutFromString(item, shortcut);
190 n->getNode("enabled")->addChangeListener(new CocoaEnabledListener(item));
191 [item setTarget:delegate];
192 [item setAction:@selector(itemAction:)];
195 item = [menu itemAtIndex:index];
196 [item setTitle:label];
199 BOOL enabled = n->getBoolValue("enabled");
200 [item setEnabled:enabled];
203 BOOST_FOREACH(SGPropertyNode_ptr binding, n->getChildren("binding")) {
204 // have to clone the bindings, since SGBinding takes ownership of the
205 // passed in node. Seems like something is wrong here, but following the
206 // PUI code for the moment.
207 SGPropertyNode* cloned(new SGPropertyNode);
208 copyProperties(binding, cloned);
209 bl.push_back(new SGBinding(cloned, globals->get_props()));
212 itemBindings[item] = bl;
214 } // of item iteration
217 void FGCocoaMenuBar::CocoaMenuBarPrivate::fireBindingsForItem(NSMenuItem *item)
219 MenuItemBindings::iterator it = itemBindings.find(item);
220 if (it == itemBindings.end()) {
224 BOOST_FOREACH(SGSharedPtr<SGBinding> b, it->second) {
229 FGCocoaMenuBar::FGCocoaMenuBar() :
230 p(new CocoaMenuBarPrivate)
235 FGCocoaMenuBar::~FGCocoaMenuBar()
240 void FGCocoaMenuBar::init()
242 CocoaAutoreleasePool pool;
244 NSMenu* mainBar = [[NSApplication sharedApplication] mainMenu];
245 SGPropertyNode_ptr props = fgGetNode("/sim/menubar/default",true);
248 NSMenuItem* previousMenu = [mainBar itemAtIndex:0];
249 if (![[previousMenu title] isEqualToString:@"FlightGear"]) {
250 [previousMenu setTitle:@"FlightGear"];
253 BOOST_FOREACH(SGPropertyNode_ptr n, props->getChildren("menu")) {
254 NSString* label = stdStringToCocoa(getLocalizedLabel(n));
255 NSMenuItem* item = [mainBar itemWithTitle:label];
259 NSInteger insertIndex = [mainBar indexOfItem:previousMenu] + 1;
260 item = [mainBar insertItemWithTitle:label action:nil keyEquivalent:@"" atIndex:insertIndex];
261 item.tag = index + 400;
263 menu = [[NSMenu alloc] init];
265 [menu setAutoenablesItems:NO];
266 [mainBar setSubmenu:menu forItem:item];
272 // synchronise menu with properties
273 p->menuFromProps(menu, n);
277 // track menu enable/disable state
278 if (!n->hasValue("enabled")) {
279 n->setBoolValue("enabled", true);
282 n->getNode("enabled")->addChangeListener(new CocoaEnabledListener(item));
286 bool FGCocoaMenuBar::isVisible() const
291 void FGCocoaMenuBar::show()
296 void FGCocoaMenuBar::hide()