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 CocoaEnabledListener : public SGPropertyChangeListener
116 CocoaEnabledListener(NSMenuItem* i) :
121 virtual void valueChanged(SGPropertyNode *node)
123 BOOL b = node->getBoolValue();
130 } // of anonymous namespace
132 FGCocoaMenuBar::CocoaMenuBarPrivate::CocoaMenuBarPrivate()
134 delegate = [[CocoaMenuDelegate alloc] init];
135 delegate.peer = this;
138 FGCocoaMenuBar::CocoaMenuBarPrivate::~CocoaMenuBarPrivate()
143 static bool labelIsSeparator(NSString* s)
145 return [s hasPrefix:@"---"];
148 void FGCocoaMenuBar::CocoaMenuBarPrivate::menuFromProps(NSMenu* menu, SGPropertyNode* menuNode)
151 BOOST_FOREACH(SGPropertyNode_ptr n, menuNode->getChildren("item")) {
152 if (!n->hasValue("enabled")) {
153 n->setBoolValue("enabled", true);
157 string l = n->getStringValue("label");
158 string::size_type pos = l.find("(");
159 if (pos != string::npos) {
161 l = full.substr(0, pos);
162 shortcut = full.substr(pos + 1, full.size() - (pos + 2));
165 NSString* label = stdStringToCocoa(strutils::simplify(l));
168 if (index >= [menu numberOfItems]) {
169 if (labelIsSeparator(label)) {
170 item = [NSMenuItem separatorItem];
173 item = [menu addItemWithTitle:label action:nil keyEquivalent:@""];
174 if (!shortcut.empty()) {
175 setItemShortcutFromString(item, shortcut);
178 n->getNode("enabled")->addChangeListener(new CocoaEnabledListener(item));
179 [item setTarget:delegate];
180 [item setAction:@selector(itemAction:)];
183 item = [menu itemAtIndex:index];
184 [item setTitle:label];
187 BOOL enabled = n->getBoolValue("enabled");
188 [item setEnabled:enabled];
191 BOOST_FOREACH(SGPropertyNode_ptr binding, n->getChildren("binding")) {
192 // have to clone the bindings, since SGBinding takes ownership of the
193 // passed in node. Seems like something is wrong here, but following the
194 // PUI code for the moment.
195 SGPropertyNode* cloned(new SGPropertyNode);
196 copyProperties(binding, cloned);
197 bl.push_back(new SGBinding(cloned, globals->get_props()));
200 itemBindings[item] = bl;
202 } // of item iteration
205 void FGCocoaMenuBar::CocoaMenuBarPrivate::fireBindingsForItem(NSMenuItem *item)
207 MenuItemBindings::iterator it = itemBindings.find(item);
208 if (it == itemBindings.end()) {
212 BOOST_FOREACH(SGSharedPtr<SGBinding> b, it->second) {
217 FGCocoaMenuBar::FGCocoaMenuBar() :
218 p(new CocoaMenuBarPrivate)
223 FGCocoaMenuBar::~FGCocoaMenuBar()
228 void FGCocoaMenuBar::init()
230 NSMenu* mainBar = [[NSApplication sharedApplication] mainMenu];
231 SGPropertyNode_ptr props = fgGetNode("/sim/menubar/default",true);
234 NSMenuItem* previousMenu = [mainBar itemAtIndex:0];
235 if (![[previousMenu title] isEqualToString:@"FlightGear"]) {
236 [previousMenu setTitle:@"FlightGear"];
239 BOOST_FOREACH(SGPropertyNode_ptr n, props->getChildren("menu")) {
240 NSString* label = stdStringToCocoa(n->getStringValue("label"));
241 NSMenuItem* item = [mainBar itemWithTitle:label];
245 NSInteger insertIndex = [mainBar indexOfItem:previousMenu] + 1;
246 item = [mainBar insertItemWithTitle:label action:nil keyEquivalent:@"" atIndex:insertIndex];
247 item.tag = index + 400;
249 menu = [[NSMenu alloc] init];
251 [menu setAutoenablesItems:NO];
252 [mainBar setSubmenu:menu forItem:item];
258 // synchronise menu with properties
259 p->menuFromProps(menu, n);
263 // track menu enable/disable state
264 if (!n->hasValue("enabled")) {
265 n->setBoolValue("enabled", true);
268 n->getNode("enabled")->addChangeListener(new CocoaEnabledListener(item));
272 bool FGCocoaMenuBar::isVisible() const
277 void FGCocoaMenuBar::show()
282 void FGCocoaMenuBar::hide()