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);
176 string l = n->getStringValue("label");
177 string::size_type pos = l.find("(");
178 if (pos != string::npos) {
180 l = full.substr(0, pos);
181 shortcut = full.substr(pos + 1, full.size() - (pos + 2));
184 NSString* label = stdStringToCocoa(strutils::simplify(l));
187 if (index >= [menu numberOfItems]) {
188 if (labelIsSeparator(label)) {
189 item = [NSMenuItem separatorItem];
192 item = [menu addItemWithTitle:label action:nil keyEquivalent:@""];
193 if (!shortcut.empty()) {
194 setItemShortcutFromString(item, shortcut);
197 n->getNode("enabled")->addChangeListener(new CocoaEnabledListener(item));
198 [item setTarget:delegate];
199 [item setAction:@selector(itemAction:)];
202 item = [menu itemAtIndex:index];
203 [item setTitle:label];
206 BOOL enabled = n->getBoolValue("enabled");
207 [item setEnabled:enabled];
210 BOOST_FOREACH(SGPropertyNode_ptr binding, n->getChildren("binding")) {
211 // have to clone the bindings, since SGBinding takes ownership of the
212 // passed in node. Seems like something is wrong here, but following the
213 // PUI code for the moment.
214 SGPropertyNode* cloned(new SGPropertyNode);
215 copyProperties(binding, cloned);
216 bl.push_back(new SGBinding(cloned, globals->get_props()));
219 itemBindings[item] = bl;
221 } // of item iteration
224 void FGCocoaMenuBar::CocoaMenuBarPrivate::fireBindingsForItem(NSMenuItem *item)
226 MenuItemBindings::iterator it = itemBindings.find(item);
227 if (it == itemBindings.end()) {
231 BOOST_FOREACH(SGSharedPtr<SGBinding> b, it->second) {
236 FGCocoaMenuBar::FGCocoaMenuBar() :
237 p(new CocoaMenuBarPrivate)
242 FGCocoaMenuBar::~FGCocoaMenuBar()
247 void FGCocoaMenuBar::init()
249 CocoaAutoreleasePool pool;
251 NSMenu* mainBar = [[NSApplication sharedApplication] mainMenu];
252 SGPropertyNode_ptr props = fgGetNode("/sim/menubar/default",true);
255 NSMenuItem* previousMenu = [mainBar itemAtIndex:0];
256 if (![[previousMenu title] isEqualToString:@"FlightGear"]) {
257 [previousMenu setTitle:@"FlightGear"];
260 BOOST_FOREACH(SGPropertyNode_ptr n, props->getChildren("menu")) {
261 NSString* label = stdStringToCocoa(n->getStringValue("label"));
262 NSMenuItem* item = [mainBar itemWithTitle:label];
266 NSInteger insertIndex = [mainBar indexOfItem:previousMenu] + 1;
267 item = [mainBar insertItemWithTitle:label action:nil keyEquivalent:@"" atIndex:insertIndex];
268 item.tag = index + 400;
270 menu = [[NSMenu alloc] init];
272 [menu setAutoenablesItems:NO];
273 [mainBar setSubmenu:menu forItem:item];
279 // synchronise menu with properties
280 p->menuFromProps(menu, n);
284 // track menu enable/disable state
285 if (!n->hasValue("enabled")) {
286 n->setBoolValue("enabled", true);
289 n->getNode("enabled")->addChangeListener(new CocoaEnabledListener(item));
293 bool FGCocoaMenuBar::isVisible() const
298 void FGCocoaMenuBar::show()
303 void FGCocoaMenuBar::hide()