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];
112 class EnabledListener : public SGPropertyChangeListener
115 EnabledListener(NSMenuItem* i) :
120 virtual void valueChanged(SGPropertyNode *node)
122 BOOL b = node->getBoolValue();
130 FGCocoaMenuBar::CocoaMenuBarPrivate::CocoaMenuBarPrivate()
132 delegate = [[CocoaMenuDelegate alloc] init];
133 delegate.peer = this;
136 FGCocoaMenuBar::CocoaMenuBarPrivate::~CocoaMenuBarPrivate()
141 static bool labelIsSeparator(NSString* s)
143 return [s hasPrefix:@"---"];
146 void FGCocoaMenuBar::CocoaMenuBarPrivate::menuFromProps(NSMenu* menu, SGPropertyNode* menuNode)
149 BOOST_FOREACH(SGPropertyNode_ptr n, menuNode->getChildren("item")) {
150 if (!n->hasValue("enabled")) {
151 n->setBoolValue("enabled", true);
155 string l = n->getStringValue("label");
156 string::size_type pos = l.find("(");
157 if (pos != string::npos) {
159 l = full.substr(0, pos);
160 shortcut = full.substr(pos + 1, full.size() - (pos + 2));
163 NSString* label = stdStringToCocoa(strutils::simplify(l));
166 if (index >= [menu numberOfItems]) {
167 if (labelIsSeparator(label)) {
168 item = [NSMenuItem separatorItem];
171 item = [menu addItemWithTitle:label action:nil keyEquivalent:@""];
172 if (!shortcut.empty()) {
173 setItemShortcutFromString(item, shortcut);
176 n->getNode("enabled")->addChangeListener(new EnabledListener(item));
177 [item setTarget:delegate];
178 [item setAction:@selector(itemAction:)];
181 item = [menu itemAtIndex:index];
182 [item setTitle:label];
185 BOOL enabled = n->getBoolValue("enabled");
186 [item setEnabled:enabled];
189 BOOST_FOREACH(SGPropertyNode_ptr binding, n->getChildren("binding")) {
190 // have to clone the bindings, since SGBinding takes ownership of the
191 // passed in node. Seems like something is wrong here, but following the
192 // PUI code for the moment.
193 SGPropertyNode* cloned(new SGPropertyNode);
194 copyProperties(binding, cloned);
195 bl.push_back(new SGBinding(cloned, globals->get_props()));
198 itemBindings[item] = bl;
200 } // of item iteration
203 void FGCocoaMenuBar::CocoaMenuBarPrivate::fireBindingsForItem(NSMenuItem *item)
205 MenuItemBindings::iterator it = itemBindings.find(item);
206 if (it == itemBindings.end()) {
210 BOOST_FOREACH(SGSharedPtr<SGBinding> b, it->second) {
215 FGCocoaMenuBar::FGCocoaMenuBar() :
216 p(new CocoaMenuBarPrivate)
221 FGCocoaMenuBar::~FGCocoaMenuBar()
226 void FGCocoaMenuBar::init()
228 NSMenu* mainBar = [[NSApplication sharedApplication] mainMenu];
229 SGPropertyNode_ptr props = fgGetNode("/sim/menubar/default",true);
232 NSMenuItem* previousMenu = [mainBar itemAtIndex:0];
233 if (![[previousMenu title] isEqualToString:@"FlightGear"]) {
234 [previousMenu setTitle:@"FlightGear"];
237 BOOST_FOREACH(SGPropertyNode_ptr n, props->getChildren("menu")) {
238 NSString* label = stdStringToCocoa(n->getStringValue("label"));
239 NSMenuItem* item = [mainBar itemWithTitle:label];
243 NSInteger insertIndex = [mainBar indexOfItem:previousMenu] + 1;
244 item = [mainBar insertItemWithTitle:label action:nil keyEquivalent:@"" atIndex:insertIndex];
245 item.tag = index + 400;
247 menu = [[NSMenu alloc] init];
249 [menu setAutoenablesItems:NO];
250 [mainBar setSubmenu:menu forItem:item];
256 // synchronise menu with properties
257 p->menuFromProps(menu, n);
261 // track menu enable/disable state
262 if (!n->hasValue("enabled")) {
263 n->setBoolValue("enabled", true);
266 n->getNode("enabled")->addChangeListener(new EnabledListener(item));
270 bool FGCocoaMenuBar::isVisible() const
275 void FGCocoaMenuBar::show()
280 void FGCocoaMenuBar::hide()